mcp-scraper 0.40.2 → 0.40.3

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 (62) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +1517 -236
  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 +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +155 -87
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +6 -5
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/bin/paa-harvest.cjs +31 -9
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +4 -4
  18. package/dist/{chunk-XORPNO3Z.js → chunk-27DUCRAZ.js} +2 -2
  19. package/dist/{chunk-44HZLHDV.js → chunk-5UN33CGU.js} +38 -1
  20. package/dist/chunk-5UN33CGU.js.map +1 -0
  21. package/dist/{chunk-X2LKCX6H.js → chunk-6J57U6HA.js} +3 -3
  22. package/dist/{chunk-LP6E462I.js → chunk-6SZ52BQ5.js} +49 -101
  23. package/dist/chunk-6SZ52BQ5.js.map +1 -0
  24. package/dist/chunk-BBI7RGOT.js +99 -0
  25. package/dist/chunk-BBI7RGOT.js.map +1 -0
  26. package/dist/{chunk-NVXNEOUQ.js → chunk-CS4HE6QY.js} +47 -11
  27. package/dist/chunk-CS4HE6QY.js.map +1 -0
  28. package/dist/{chunk-3PIWJS6Y.js → chunk-EHES33KB.js} +2 -2
  29. package/dist/{chunk-GUVKHCKE.js → chunk-IQTT7CAB.js} +94 -3
  30. package/dist/chunk-IQTT7CAB.js.map +1 -0
  31. package/dist/{chunk-7XBBFBYY.js → chunk-JVIW4GK2.js} +44 -12
  32. package/dist/chunk-JVIW4GK2.js.map +1 -0
  33. package/dist/chunk-XU4ZLIB2.js +7 -0
  34. package/dist/chunk-XU4ZLIB2.js.map +1 -0
  35. package/dist/{db-W3CP562I.js → db-LVVU6NOK.js} +16 -2
  36. package/dist/{extract-bundle-K4PG3RZJ.js → extract-bundle-SKXEDG4Z.js} +4 -4
  37. package/dist/index.cjs +31 -9
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.js +4 -4
  40. package/dist/{location-data-repository-RLQX6SNM.js → location-data-repository-NBTCOA7B.js} +3 -3
  41. package/dist/{server-KM3CFWCF.js → server-W76RUYD3.js} +1108 -66
  42. package/dist/server-W76RUYD3.js.map +1 -0
  43. package/dist/{site-extract-repository-2SMMFKKL.js → site-extract-repository-6GX72U4N.js} +4 -4
  44. package/dist/{worker-FXAGFYOE.js → worker-5RWLFSAJ.js} +27 -28
  45. package/dist/worker-5RWLFSAJ.js.map +1 -0
  46. package/package.json +1 -1
  47. package/dist/chunk-44HZLHDV.js.map +0 -1
  48. package/dist/chunk-7XBBFBYY.js.map +0 -1
  49. package/dist/chunk-GUVKHCKE.js.map +0 -1
  50. package/dist/chunk-LP6E462I.js.map +0 -1
  51. package/dist/chunk-NVXNEOUQ.js.map +0 -1
  52. package/dist/chunk-ZUJLSICT.js +0 -7
  53. package/dist/chunk-ZUJLSICT.js.map +0 -1
  54. package/dist/server-KM3CFWCF.js.map +0 -1
  55. package/dist/worker-FXAGFYOE.js.map +0 -1
  56. /package/dist/{chunk-XORPNO3Z.js.map → chunk-27DUCRAZ.js.map} +0 -0
  57. /package/dist/{chunk-X2LKCX6H.js.map → chunk-6J57U6HA.js.map} +0 -0
  58. /package/dist/{chunk-3PIWJS6Y.js.map → chunk-EHES33KB.js.map} +0 -0
  59. /package/dist/{db-W3CP562I.js.map → db-LVVU6NOK.js.map} +0 -0
  60. /package/dist/{extract-bundle-K4PG3RZJ.js.map → extract-bundle-SKXEDG4Z.js.map} +0 -0
  61. /package/dist/{location-data-repository-RLQX6SNM.js.map → location-data-repository-NBTCOA7B.js.map} +0 -0
  62. /package/dist/{site-extract-repository-2SMMFKKL.js.map → site-extract-repository-6GX72U4N.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli.ts","../../src/lib/browser-service-env.ts","../../src/schemas.ts","../../src/driver/BrowserDriver.ts","../../src/selectors.ts","../../src/errors.ts","../../src/api/cost-telemetry.ts","../../src/api/db.ts","../../src/api/cost-context.ts","../../src/api/cost-rates.ts","../../src/locations.ts","../../src/uule.ts","../../src/serp-location-query.ts","../../src/serp-location-debug.ts","../../src/lib/paa-answer-cleanup.ts","../../src/extractor/ai-surfaces.ts","../../src/extractor/PAAExtractor.ts","../../src/output/OutputSerializer.ts","../../src/output/memory-library-sink.ts","../../src/output/ProgressReporter.ts","../../src/kernel-proxy-resolver.ts","../../src/backup-proxy.ts","../../src/harvest.ts","../../bin/paa-harvest.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { browserServiceApiKey } from './lib/browser-service-env.js'\nimport { harvest } from './harvest.js'\n\nconst program = new Command()\n\nprogram\n .name('paa-harvest')\n .description('Recursively extract Google People Also Ask questions')\n .requiredOption('-q, --query <query>', 'Seed query')\n .option('-l, --location <location>', 'Location name (e.g. \"austin\" or \"Austin,Texas,United States\")')\n .option('--gl <gl>', 'Google country code', 'us')\n .option('--hl <hl>', 'Google language code', 'en')\n .option('-d, --depth <depth>', 'BFS depth (1-30)', '3')\n .option('-m, --max-questions <n>', 'Max questions to harvest', '100')\n .option('-o, --output <dir>', 'Output directory', './paa-output')\n .option('-f, --format <format>', 'Output format: json, csv, or both', 'both')\n .option('--headless', 'Run browser in headless mode', false)\n .option('--profile <dir>', 'Persistent browser profile directory')\n .option('--proxy <url>', 'Proxy server URL')\n .option('--kernel-api-key <key>', 'Browser service API key (or set BROWSER_SERVICE_API_KEY env var)')\n .action(async (opts) => {\n try {\n const result = await harvest({\n query: opts.query,\n location: opts.location,\n gl: opts.gl,\n hl: opts.hl,\n depth: parseInt(opts.depth, 10),\n maxQuestions: parseInt(opts.maxQuestions, 10),\n outputDir: opts.output,\n format: opts.format,\n headless: opts.headless,\n profileDir: opts.profile,\n proxy: opts.proxy,\n kernelApiKey: opts.kernelApiKey ?? browserServiceApiKey(),\n })\n console.log(JSON.stringify({ totalQuestions: result.totalQuestions, outputDir: result.stats.seed }))\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n })\n\nexport async function runCli(): Promise<void> {\n await program.parseAsync()\n}\n","export function browserServiceApiKey(): string | undefined {\n const value = (process.env.BROWSER_SERVICE_API_KEY ?? process.env.KERNEL_API_KEY)?.trim()\n return value || undefined\n}\n\nexport function browserServiceProxyId(): string | undefined {\n return undefined\n}\n\nexport function browserServiceProfileName(): string | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_NAME ??\n process.env.BROWSER_SERVICE_PROFILE_NAME ??\n process.env.KERNEL_BROWSER_PROFILE_NAME ??\n process.env.KERNEL_PROFILE_NAME\n )?.trim()\n return value || undefined\n}\n\nexport function browserServiceProfileSaveChanges(): boolean | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_SAVE_CHANGES ??\n process.env.BROWSER_SERVICE_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_BROWSER_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_PROFILE_SAVE_CHANGES\n )?.trim().toLowerCase()\n if (!value) return undefined\n if (['1', 'true', 'yes', 'on'].includes(value)) return true\n if (['0', 'false', 'no', 'off'].includes(value)) return false\n return undefined\n}\n","import { z } from 'zod'\n\nexport const DEFAULT_PROXY_MODE = 'none' as const\nexport const DEFAULT_MAPS_PROXY_MODE = 'none' as const\n\nexport const HarvestOptionsSchema = z.object({\n query: z.string().min(1),\n location: z.string().optional(),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n device: z.enum(['desktop', 'mobile']).default('desktop'),\n proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).optional(),\n keepDefaultProxy: z.boolean().optional(),\n requireUsEgress: z.boolean().optional(),\n maxAttempts: z.number().int().min(1).max(12).optional(),\n debug: z.boolean().default(false),\n depth: z.number().int().min(1).max(30).default(3),\n maxQuestions: z.number().int().min(1).max(1000).default(100),\n headless: z.boolean().default(false),\n profileDir: z.string().optional(),\n proxy: z.string().url().optional(),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n 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 recency: z.enum(['day', 'week', 'month', 'year']).optional(),\n softDeadlineMs: z.number().optional(),\n})\n\nexport function recencyToTbs(recency: 'day' | 'week' | 'month' | 'year' | undefined): string | undefined {\n switch (recency) {\n case 'day': return 'qdr:d'\n case 'week': return 'qdr:w'\n case 'month': return 'qdr:m'\n case 'year': return 'qdr:y'\n default: return undefined\n }\n}\n\nexport const MapsPlaceOptionsSchema = z.object({\n businessName: z.string().min(1),\n location: z.string().min(1),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n includeReviews: z.boolean().default(false),\n maxReviews: z.number().int().min(1).max(500).default(50),\n includeServices: z.boolean().default(false),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n headless: z.boolean().default(true),\n})\n\nexport const MapsSearchOptionsSchema = z.object({\n query: z.string().min(1),\n location: z.string().optional(),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n maxResults: z.number().int().min(1).max(50).default(10),\n includeServices: z.boolean().default(false),\n proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_MAPS_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).optional(),\n serpRedirect: z.boolean().optional(),\n forceDirectEgress: z.boolean().optional(),\n debug: z.boolean().default(false),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n kernelProxyResolution: z.unknown().optional(),\n headless: z.boolean().default(true),\n})\n\nexport const RawPAAItemSchema = z.object({\n question: z.string().min(1),\n answer: z.string().optional(),\n sourceTitle: z.string().optional(),\n sourceSite: z.string().optional(),\n sourceCite: z.string().optional(),\n})\n\nexport const RawMapsOverviewSchema = z.object({\n name: z.string().nullable(),\n rating: z.string().nullable(),\n reviewCount: z.string().nullable(),\n category: z.string().nullable(),\n address: z.string().nullable(),\n hoursSummary: z.string().nullable(),\n phone: z.string().nullable(),\n phoneDisplay: z.string().nullable(),\n website: z.string().nullable(),\n plusCode: z.string().nullable(),\n bookingUrl: z.string().nullable(),\n})\n\nexport const RawMapsHoursRowSchema = z.object({\n day: z.string(),\n hours: z.string(),\n})\n\nexport const RawMapsReviewStatsSchema = z.object({\n reviewHistogram: z.array(z.object({\n stars: z.number(),\n count: z.string(),\n })),\n reviewTopics: z.array(z.object({\n label: z.string(),\n count: z.string(),\n })),\n})\n\nexport const RawMapsReviewCardSchema = z.object({\n reviewId: z.string(),\n author: z.string().nullable(),\n stars: z.string().nullable(),\n date: z.string().nullable(),\n text: z.string().nullable(),\n ownerResponse: z.string().nullable(),\n})\n\nexport const RawMapsAboutAttributeSchema = z.object({\n section: z.string(),\n attribute: z.string(),\n})\n","import { chromium } from 'playwright-extra'\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\nimport { chromium as playwrightChromium } from 'playwright'\nimport type { Browser, BrowserContext, Page } from 'playwright'\nimport Kernel from '@onkernel/sdk'\nimport { PAASelectors } from '../selectors.js'\nimport { CaptchaError, ExtractionError, RECAPTCHA_INSTRUCTIONS } from '../errors.js'\nimport type { BrowserDebugSnapshot, BrowserNetworkLocationDebug, DriverConfig, KernelSessionDebug, ProxyMode, SerpDevice, SerpNavigationDebug, UuleString } from '../types.js'\nimport type { BrowserCloseResult, IBrowserDriver } from './IBrowserDriver.js'\nimport { recordKernelSession } from '../api/cost-telemetry.js'\nimport { currentCostContext } from '../api/cost-context.js'\nimport { HEADLESS_OPS } from '../api/cost-rates.js'\n\nchromium.use(StealthPlugin())\n\nconst DESKTOP_USER_AGENT =\n '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'\n\nconst MOBILE_USER_AGENT =\n 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1'\n\nconst DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS = 180\nconst KERNEL_BROWSER_CLOSE_TIMEOUT_MS = 3_000\nconst KERNEL_SESSION_DELETE_TIMEOUT_MS = 5_000\n\nfunction positiveIntFromEnv(name: string, fallback: number): number {\n const raw = process.env[name]\n if (!raw) return fallback\n const parsed = Number(raw)\n return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback\n}\n\nfunction proxyIdSuffix(proxyId: string | null | undefined): string | null {\n return proxyId ? proxyId.slice(-6) : null\n}\n\nexport function serpArrivalUrl(googleUrl: string, baseOverride?: string): string {\n const base = (baseOverride ?? process.env.SERP_REDIRECT_BASE)?.trim().replace(/\\/+$/, '')\n if (!base) return googleUrl\n return `${base}/g/${Buffer.from(googleUrl, 'utf8').toString('base64url')}`\n}\n\nexport function isUsCountry(country: string | null | undefined): boolean {\n if (!country) return false\n return /united states|u\\.?s\\.?a?\\.?|^us$/i.test(country.trim())\n}\n\nfunction errorText(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction isKernelProfileConflict(err: unknown): boolean {\n return /\\b409\\b|conflict|already exists/i.test(errorText(err))\n}\n\nasync function ensureKernelProfile(kernelClient: Kernel, name: string): Promise<void> {\n try {\n await kernelClient.profiles.create({ name })\n } catch (err) {\n if (!isKernelProfileConflict(err)) throw err\n }\n}\n\nexport function rankCheckContextOptions(config: DriverConfig) {\n return {\n viewport: config.viewport,\n locale: config.locale,\n userAgent: config.userAgent ?? (config.isMobile ? MOBILE_USER_AGENT : DESKTOP_USER_AGENT),\n ...(config.deviceScaleFactor ? { deviceScaleFactor: config.deviceScaleFactor } : {}),\n ...(config.isMobile !== undefined ? { isMobile: config.isMobile } : {}),\n ...(config.hasTouch !== undefined ? { hasTouch: config.hasTouch } : {}),\n }\n}\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | undefined\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)\n }),\n ])\n } finally {\n if (timeout) clearTimeout(timeout)\n }\n}\n\nexport function buildYouTubeChannelVideosUrl(channelInput: string): string {\n const raw = channelInput.trim()\n if (!raw) throw new Error('channelHandle is required')\n\n const urlLike = /^https?:\\/\\//i.test(raw) || /^(www\\.|m\\.)?youtube\\.com\\//i.test(raw)\n if (urlLike) {\n const parsed = new URL(/^https?:\\/\\//i.test(raw) ? raw : `https://${raw}`)\n const host = parsed.hostname.replace(/^www\\./, '').replace(/^m\\./, '').toLowerCase()\n if (host !== 'youtube.com') throw new Error('channel URL must be on youtube.com')\n\n const segments = parsed.pathname.split('/').filter(Boolean)\n const first = segments[0] ?? ''\n const second = segments[1] ?? ''\n\n if (first.startsWith('@')) return `https://www.youtube.com/${first}/videos`\n if (first === 'channel' && second) return `https://www.youtube.com/channel/${second}/videos`\n if ((first === 'c' || first === 'user') && second) return `https://www.youtube.com/${first}/${second}/videos`\n\n throw new Error('channel URL must be a YouTube handle, /channel/UC..., /c/..., or /user/... URL')\n }\n\n const stripped = raw.replace(/^\\/+/, '').replace(/\\/+$/, '')\n const withoutVideos = stripped.replace(/\\/videos$/i, '')\n if (/^UC[\\w-]{20,}$/.test(withoutVideos)) {\n return `https://www.youtube.com/channel/${withoutVideos}/videos`\n }\n\n const handle = withoutVideos.startsWith('@') ? withoutVideos : `@${withoutVideos}`\n if (!/^@[\\w.-]+$/.test(handle)) {\n throw new Error('channelHandle must be an @handle, UC channel ID, or YouTube channel URL')\n }\n return `https://www.youtube.com/${handle}/videos`\n}\n\nexport class BrowserDriver implements IBrowserDriver {\n private browser: Browser | null = null\n private context: BrowserContext | null = null\n private page: Page | null = null\n private kernelClient: Kernel | null = null\n private kernelSessionId: string | null = null\n private kernelOpenedAtMs: number | null = null\n private kernelStealth: boolean | null = null\n private kernelProxyUsed: boolean | null = null\n private kernelProxySource: string | null = null\n private kernelProxyType: string | null = null\n private kernelHeadlessSent: boolean | null = null\n private debugEnabled = false\n private debugSnapshot: BrowserDebugSnapshot = {\n kernel: null,\n context: null,\n networkLocation: null,\n serpNavigation: null,\n }\n\n async launch(config: DriverConfig): Promise<void> {\n this.debugEnabled = config.debug === true\n const proxyMode: ProxyMode = config.proxyMode ?? (config.kernelProxyId ? 'configured' : 'none')\n const device: SerpDevice = config.isMobile ? 'mobile' : 'desktop'\n this.debugSnapshot = {\n kernel: null,\n context: {\n viewport: config.viewport,\n locale: config.locale,\n device,\n userAgent: config.userAgent ?? (config.isMobile ? MOBILE_USER_AGENT : DESKTOP_USER_AGENT),\n deviceScaleFactor: config.deviceScaleFactor ?? null,\n isMobile: config.isMobile === true,\n hasTouch: config.hasTouch === true,\n },\n networkLocation: null,\n serpNavigation: null,\n }\n\n if (config.kernelApiKey) {\n this.kernelClient = new Kernel({ apiKey: config.kernelApiKey })\n const timeoutSeconds = positiveIntFromEnv('KERNEL_BROWSER_TIMEOUT_SECONDS', DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS)\n if (config.kernelProfileName && config.kernelProfileSaveChanges === true) {\n await ensureKernelProfile(this.kernelClient, config.kernelProfileName)\n }\n const costCtx = currentCostContext()\n let useHeadless: boolean\n if (config.headlessMode === 'headful' || costCtx?.forceHeadful === true) useHeadless = false\n else if (config.headlessMode === 'headless') useHeadless = true\n else useHeadless = costCtx?.forceHeadless === true\n || (process.env.KERNEL_HEADLESS_DISABLED !== 'true' && !!costCtx?.op && HEADLESS_OPS.has(costCtx.op))\n this.kernelHeadlessSent = useHeadless ? true : null\n if (costCtx?.headlessSentOut) costCtx.headlessSentOut.value = this.kernelHeadlessSent\n const kernelBrowser = await this.kernelClient.browsers.create({\n stealth: true,\n timeout_seconds: timeoutSeconds,\n ...(useHeadless ? { headless: true } : {}),\n ...(config.kernelProxyId ? { proxy_id: config.kernelProxyId } : {}),\n ...(config.kernelProfileName\n ? {\n profile: {\n name: config.kernelProfileName,\n ...(typeof config.kernelProfileSaveChanges === 'boolean' ? { save_changes: config.kernelProfileSaveChanges } : {}),\n },\n }\n : {}),\n })\n this.kernelSessionId = kernelBrowser.session_id\n this.kernelOpenedAtMs = Date.now()\n this.kernelStealth = typeof kernelBrowser.stealth === 'boolean' ? kernelBrowser.stealth : null\n this.kernelProxyUsed = typeof kernelBrowser.proxy_id === 'string' ? Boolean(kernelBrowser.proxy_id) : Boolean(config.kernelProxyId)\n this.kernelProxySource = config.kernelProxyResolution?.source ?? null\n this.kernelProxyType = config.kernelProxyResolution?.proxyType ?? null\n let defaultProxyDisabled: boolean | null = null\n let defaultProxyDisableError: string | null = null\n if (!config.kernelProxyId && !config.keepDefaultProxy) {\n try {\n await withTimeout(\n this.kernelClient.browsers.update(this.kernelSessionId, { disable_default_proxy: true }),\n 5_000,\n `Kernel session ${this.kernelSessionId} disable default proxy`,\n )\n defaultProxyDisabled = true\n } catch (err) {\n defaultProxyDisabled = false\n defaultProxyDisableError = errorText(err)\n }\n }\n const kernelDebug: KernelSessionDebug = {\n sessionId: this.kernelSessionId,\n proxyMode,\n requestedProxyIdPresent: Boolean(config.kernelProxyId),\n requestedProxyIdSuffix: proxyIdSuffix(config.kernelProxyId),\n createdProxyIdPresent: typeof kernelBrowser.proxy_id === 'string' ? Boolean(kernelBrowser.proxy_id) : null,\n createdProxyIdSuffix: proxyIdSuffix(kernelBrowser.proxy_id),\n retrievedProxyIdPresent: null,\n retrievedProxyIdSuffix: null,\n retrievedProxyIdMatchesRequested: null,\n defaultProxyDisabled,\n defaultProxyDisableError,\n proxyResolution: config.kernelProxyResolution ?? null,\n timeoutSeconds,\n stealth: typeof kernelBrowser.stealth === 'boolean' ? kernelBrowser.stealth : null,\n profilePresent: null,\n poolPresent: null,\n retrieveError: null,\n }\n this.debugSnapshot.kernel = kernelDebug\n console.info(JSON.stringify({\n event: 'kernel_browser_created',\n kernel_session_id: this.kernelSessionId,\n timeout_seconds: timeoutSeconds,\n proxy_mode: proxyMode,\n proxy_id_present: Boolean(config.kernelProxyId),\n profile_name_present: Boolean(config.kernelProfileName),\n proxy_resolution_source: config.kernelProxyResolution?.source,\n }))\n if (this.debugEnabled) {\n await this.populateKernelRetrieveDebug(kernelDebug, config.kernelProxyId)\n }\n this.browser = await playwrightChromium.connectOverCDP(kernelBrowser.cdp_ws_url)\n this.context = await this.browser.newContext(rankCheckContextOptions(config))\n await this.installEsbuildHelperShims(this.context)\n this.page = await this.context.newPage()\n await this.page.setViewportSize(config.viewport)\n if (this.debugEnabled || config.requireUsEgress) {\n this.debugSnapshot.networkLocation = await this.captureBrowserNetworkLocation()\n }\n if (config.requireUsEgress) {\n const country = this.debugSnapshot.networkLocation?.country ?? null\n if (!isUsCountry(country)) {\n throw new CaptchaError(`Non-US egress (${country ?? 'unknown'}) — retrying with a fresh session`)\n }\n }\n return\n }\n\n const launchOpts = {\n headless: config.headless,\n proxy: config.proxy ? { server: config.proxy } : undefined,\n ...(config.browserExecutablePath ? { executablePath: config.browserExecutablePath } : {}),\n ...(config.ignoreDefaultArgs ? { ignoreDefaultArgs: config.ignoreDefaultArgs } : {}),\n }\n const ctxOpts = rankCheckContextOptions(config)\n if (config.profileDir) {\n this.context = await chromium.launchPersistentContext(config.profileDir, {\n ...launchOpts,\n ...ctxOpts,\n })\n await this.installEsbuildHelperShims(this.context)\n this.page = await this.context.newPage()\n } else {\n this.browser = await chromium.launch(launchOpts)\n this.context = await this.browser.newContext(ctxOpts)\n await this.installEsbuildHelperShims(this.context)\n this.page = await this.context.newPage()\n }\n if (this.debugEnabled) {\n this.debugSnapshot.networkLocation = await this.captureBrowserNetworkLocation()\n }\n }\n\n private async populateKernelRetrieveDebug(kernelDebug: KernelSessionDebug, requestedProxyId: string | undefined): Promise<void> {\n if (!this.kernelClient || !this.kernelSessionId) return\n try {\n const retrieved = await withTimeout(\n this.kernelClient.browsers.retrieve(this.kernelSessionId),\n 5_000,\n `Kernel session ${this.kernelSessionId} retrieve`,\n ) as { proxy_id?: string; timeout_seconds?: number; stealth?: boolean; profile?: unknown; pool?: unknown }\n kernelDebug.retrievedProxyIdPresent = typeof retrieved.proxy_id === 'string' ? Boolean(retrieved.proxy_id) : false\n kernelDebug.retrievedProxyIdSuffix = proxyIdSuffix(retrieved.proxy_id)\n kernelDebug.retrievedProxyIdMatchesRequested = requestedProxyId\n ? retrieved.proxy_id === requestedProxyId\n : !retrieved.proxy_id\n kernelDebug.timeoutSeconds = typeof retrieved.timeout_seconds === 'number' ? retrieved.timeout_seconds : kernelDebug.timeoutSeconds\n kernelDebug.stealth = typeof retrieved.stealth === 'boolean' ? retrieved.stealth : kernelDebug.stealth\n kernelDebug.profilePresent = Boolean(retrieved.profile)\n kernelDebug.poolPresent = Boolean(retrieved.pool)\n } catch (err) {\n kernelDebug.retrieveError = errorText(err)\n }\n }\n\n private async captureBrowserNetworkLocation(): Promise<BrowserNetworkLocationDebug> {\n const fallback = (message: string, source: BrowserNetworkLocationDebug['source'] = 'ipapi.co'): BrowserNetworkLocationDebug => ({\n source,\n ip: null,\n city: null,\n region: null,\n country: null,\n org: null,\n timezone: null,\n error: message,\n })\n if (!this.context) return fallback('browser context is not available')\n let debugPage: Page | null = null\n try {\n debugPage = await this.context.newPage()\n const ipwho = await this.loadJsonInDebugPage(debugPage, 'https://ipwho.is/')\n if (ipwho) {\n const connection = typeof ipwho.connection === 'object' && ipwho.connection !== null\n ? ipwho.connection as Record<string, unknown>\n : {}\n return {\n source: 'ipwho.is',\n ip: typeof ipwho.ip === 'string' ? ipwho.ip : null,\n city: typeof ipwho.city === 'string' ? ipwho.city : null,\n region: typeof ipwho.region === 'string' ? ipwho.region : null,\n country: typeof ipwho.country === 'string' ? ipwho.country : null,\n org: typeof connection.org === 'string' ? connection.org : null,\n timezone: typeof ipwho.timezone === 'object' && ipwho.timezone !== null && typeof (ipwho.timezone as Record<string, unknown>).id === 'string'\n ? (ipwho.timezone as Record<string, unknown>).id as string\n : null,\n error: null,\n }\n }\n\n const ipify = await this.loadJsonInDebugPage(debugPage, 'https://api64.ipify.org?format=json')\n if (ipify) {\n return {\n source: 'api64.ipify.org',\n ip: typeof ipify.ip === 'string' ? ipify.ip : null,\n city: null,\n region: null,\n country: null,\n org: null,\n timezone: null,\n error: null,\n }\n }\n\n await withTimeout(\n debugPage.goto('https://ipapi.co/json/', { waitUntil: 'domcontentloaded', timeout: 7_000 }),\n 8_000,\n 'browser network location navigation',\n )\n const body = await debugPage.locator('body').innerText({ timeout: 2_000 })\n const data = JSON.parse(body) as Record<string, unknown>\n return {\n source: 'ipapi.co',\n ip: typeof data.ip === 'string' ? data.ip : null,\n city: typeof data.city === 'string' ? data.city : null,\n region: typeof data.region === 'string' ? data.region : null,\n country: typeof data.country_name === 'string' ? data.country_name : typeof data.country === 'string' ? data.country : null,\n org: typeof data.org === 'string' ? data.org : null,\n timezone: typeof data.timezone === 'string' ? data.timezone : null,\n error: null,\n }\n } catch (err) {\n return fallback(errorText(err))\n } finally {\n await debugPage?.close().catch(() => {})\n }\n }\n\n private async loadJsonInDebugPage(debugPage: Page, url: string): Promise<Record<string, unknown> | null> {\n try {\n await withTimeout(\n debugPage.goto(url, { waitUntil: 'domcontentloaded', timeout: 7_000 }),\n 8_000,\n `browser network location navigation ${url}`,\n )\n const body = await debugPage.locator('body').innerText({ timeout: 2_000 })\n return JSON.parse(body) as Record<string, unknown>\n } catch {\n return null\n }\n }\n\n private async installEsbuildHelperShims(context: BrowserContext): Promise<void> {\n await context.addInitScript(() => {\n const g = globalThis as Record<string, unknown>\n if (typeof g.__name !== 'function') g.__name = (fn: unknown) => fn\n if (typeof g.__publicField !== 'function') g.__publicField = (obj: Record<string, unknown>, key: string, value: unknown) => { obj[key] = value; return value }\n })\n }\n\n async navigateToSERP(\n query: string,\n uule: UuleString | null,\n gl: string,\n hl: string,\n options?: { num?: number; debug?: boolean; tbs?: string },\n ): Promise<{ hasPaa: boolean }> {\n const params = new URLSearchParams({ q: query, gl, hl, pws: '0' })\n if (options?.num) params.set('num', String(options.num))\n if (options?.tbs) params.set('tbs', options.tbs)\n if (uule) params.set('uule', uule)\n const url = 'https://www.google.com/search?' + params.toString()\n const navUrl = serpArrivalUrl(url)\n const navDebug: SerpNavigationDebug | null = options?.debug\n ? {\n requestedUrl: url,\n finalUrl: null,\n title: null,\n bodySnippet: null,\n hasPaa: null,\n captchaDetected: null,\n googleSorryUrl: null,\n redirected: null,\n }\n : null\n if (navDebug) this.debugSnapshot.serpNavigation = navDebug\n\n try {\n await this.page!.goto(navUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 })\n } catch (err) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: null, captchaDetected: null })\n const diag = await this.captureDiagnostics(url)\n throw new ExtractionError(`page.goto failed: ${(err as Error).message} | ${diag}`)\n }\n\n if (await this.isSerpChallengeVisible()) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true })\n throw new CaptchaError(this.captchaMessage())\n }\n\n const fastFound = await this.page!.waitForSelector(PAASelectors.item, { timeout: 4_000 }).catch(() => null)\n if (fastFound) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: true, captchaDetected: false })\n return { hasPaa: true }\n }\n\n if (await this.isSerpChallengeVisible()) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true })\n throw new CaptchaError(this.captchaMessage())\n }\n\n for (let i = 1; i <= 6; i++) {\n await this.page!.evaluate((f: number) => { window.scrollTo(0, document.body.scrollHeight * f) }, i / 6)\n await this.page!.waitForTimeout(600)\n const count = await this.page!.locator(PAASelectors.item).count()\n if (count > 0) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: true, captchaDetected: false })\n return { hasPaa: true }\n }\n }\n\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: false })\n return { hasPaa: false }\n }\n\n private async isSerpChallengeVisible(): Promise<boolean> {\n if (!this.page) return false\n const captchaCount = await this.page.locator(PAASelectors.captchaMarker).count().catch(() => 0)\n if (captchaCount > 0) return true\n const finalUrl = this.page.url()\n if (/google\\.[^/]+\\/sorry\\//i.test(finalUrl)) return true\n const bodySnippet = await this.page.evaluate(() => {\n const text = (document.body?.innerText ?? '').replace(/\\s+/g, ' ').trim()\n return text.slice(0, 1000)\n }).catch(() => '')\n return /recaptcha|unusual traffic|are you a robot|about this page|detected unusual traffic/i.test(bodySnippet)\n }\n\n private async updateSerpNavigationDebug(\n navDebug: SerpNavigationDebug | null,\n requestedUrl: string,\n state: { hasPaa: boolean | null; captchaDetected: boolean | null },\n ): Promise<void> {\n if (!navDebug || !this.page) return\n try {\n const finalUrl = this.page.url()\n const title = await this.page.title().catch(() => '')\n const bodySnippet = await this.page.evaluate(() => {\n const text = (document.body?.innerText ?? '').replace(/\\s+/g, ' ').trim()\n return text.slice(0, 500)\n }).catch(() => '')\n const textCaptcha = /recaptcha|unusual traffic|are you a robot/i.test(bodySnippet)\n navDebug.finalUrl = finalUrl\n navDebug.title = title\n navDebug.bodySnippet = bodySnippet\n navDebug.hasPaa = state.hasPaa\n navDebug.captchaDetected = state.captchaDetected ?? textCaptcha\n navDebug.googleSorryUrl = /google\\.[^/]+\\/sorry\\//i.test(finalUrl)\n navDebug.redirected = finalUrl !== requestedUrl\n } catch (err) {\n navDebug.bodySnippet = `debug capture failed: ${errorText(err)}`\n }\n }\n\n private async captureDiagnostics(intendedUrl: string): Promise<string> {\n try {\n const finalUrl = this.page!.url()\n const title = await this.page!.title().catch(() => '')\n const bodySnippet = await this.page!.evaluate(() => {\n const t = (document.body?.innerText ?? '').replace(/\\s+/g, ' ').trim()\n return t.slice(0, 400)\n }).catch(() => '')\n const consent = /consent\\.google\\./.test(finalUrl) || /before you continue/i.test(bodySnippet)\n const recaptcha = /recaptcha|unusual traffic|are you a robot/i.test(bodySnippet)\n const flags = [\n consent ? 'CONSENT_WALL' : '',\n recaptcha ? 'BOT_CHALLENGE' : '',\n finalUrl !== intendedUrl ? 'REDIRECTED' : '',\n ].filter(Boolean).join(',')\n return `intended=${intendedUrl} | final=${finalUrl} | title=\"${title}\" | flags=[${flags}] | body=\"${bodySnippet}\"`\n } catch (e) {\n return `diagnostics-failed: ${(e as Error).message}`\n }\n }\n\n private captchaMessage(): string {\n return this.kernelClient\n ? 'Google returned a CAPTCHA on this session — retrying with a fresh session.'\n : RECAPTCHA_INSTRUCTIONS\n }\n\n async navigateTo(url: string): Promise<void> {\n const arrivalBase = process.env.ADS_ARRIVAL_REDIRECT_BASE?.trim()\n const navUrl = arrivalBase ? serpArrivalUrl(url, arrivalBase) : url\n try {\n await this.page!.goto(navUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 })\n if (/google\\.[^/]+\\/search\\?/i.test(url) && await this.isSerpChallengeVisible()) {\n throw new CaptchaError(this.captchaMessage())\n }\n } catch (err) {\n if (err instanceof CaptchaError) throw err\n const diag = await this.captureDiagnostics(url)\n throw new ExtractionError(`page.goto failed: ${(err as Error).message} | ${diag}`)\n }\n }\n\n async navigateToChannel(channelHandle: string): Promise<void> {\n const url = buildYouTubeChannelVideosUrl(channelHandle)\n try {\n await this.page!.goto(url, { waitUntil: 'networkidle', timeout: 30_000 })\n } catch (err) {\n const diag = await this.captureDiagnostics(url)\n throw new ExtractionError(`navigateToChannel failed: ${(err as Error).message} | ${diag}`)\n }\n }\n\n async evaluate<T>(fn: (...args: unknown[]) => T | Promise<T>, arg?: unknown): Promise<T> {\n return (this.page! as { evaluate(fn: unknown, arg?: unknown): Promise<T> }).evaluate(fn, arg)\n }\n\n getPage(): Page {\n return this.page!\n }\n\n getKernelSessionId(): string | null {\n return this.kernelSessionId\n }\n\n getDebugSnapshot(): BrowserDebugSnapshot {\n return this.debugSnapshot\n }\n\n async close(): Promise<BrowserCloseResult> {\n if (this.browser) {\n const b = this.browser\n const sessionId = this.kernelSessionId\n const client = this.kernelClient\n const openedAtMs = this.kernelOpenedAtMs\n const stealth = this.kernelStealth\n const proxyUsed = this.kernelProxyUsed\n const proxySource = this.kernelProxySource\n const proxyType = this.kernelProxyType\n const headlessSent = this.kernelHeadlessSent\n this.browser = null\n this.context = null\n this.page = null\n this.kernelSessionId = null\n this.kernelClient = null\n this.kernelOpenedAtMs = null\n this.kernelHeadlessSent = null\n this.kernelProxySource = null\n this.kernelProxyType = null\n if (client && sessionId && openedAtMs != null) {\n void recordKernelSession({ kernelSessionId: sessionId, source: 'browser_driver', stealth, headlessSent, proxyUsed, proxySource, proxyType, openedAtMs, closedAtMs: Date.now() })\n }\n if (client && sessionId) {\n console.info(JSON.stringify({\n event: 'kernel_browser_delete_started',\n kernel_session_id: sessionId,\n }))\n const deleteSession = withTimeout(\n client.browsers.deleteByID(sessionId),\n KERNEL_SESSION_DELETE_TIMEOUT_MS,\n `Kernel session ${sessionId} delete`,\n )\n const closeBrowser = withTimeout(\n b.close(),\n KERNEL_BROWSER_CLOSE_TIMEOUT_MS,\n `Kernel browser ${sessionId} close`,\n )\n const [deleteResult, closeResult] = await Promise.allSettled([deleteSession, closeBrowser])\n const result: BrowserCloseResult = {\n kernelSessionId: sessionId,\n kernelDeleteStarted: true,\n kernelDeleteSucceeded: deleteResult.status === 'fulfilled',\n kernelDeleteError: deleteResult.status === 'rejected'\n ? (deleteResult.reason instanceof Error ? deleteResult.reason.message : String(deleteResult.reason))\n : null,\n browserCloseSucceeded: closeResult.status === 'fulfilled',\n browserCloseError: closeResult.status === 'rejected'\n ? (closeResult.reason instanceof Error ? closeResult.reason.message : String(closeResult.reason))\n : null,\n }\n if (deleteResult.status === 'rejected') {\n console.warn(JSON.stringify({\n event: 'kernel_browser_delete_failed',\n kernel_session_id: sessionId,\n message: result.kernelDeleteError,\n }))\n console.warn(`Kernel session cleanup failed for ${sessionId}:`, deleteResult.reason)\n } else {\n console.info(JSON.stringify({\n event: 'kernel_browser_delete_succeeded',\n kernel_session_id: sessionId,\n }))\n }\n if (closeResult.status === 'rejected') {\n console.warn(JSON.stringify({\n event: 'kernel_browser_close_failed',\n kernel_session_id: sessionId,\n message: result.browserCloseError,\n }))\n console.warn(`Kernel browser close failed for ${sessionId}:`, closeResult.reason)\n }\n return result\n }\n await b.close()\n return {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: true,\n browserCloseError: null,\n }\n } else if (this.context) {\n const ctx = this.context\n this.context = null\n this.page = null\n await ctx.close()\n return {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: true,\n browserCloseError: null,\n }\n }\n return {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: null,\n browserCloseError: null,\n }\n }\n}\n","export const PAASelectors = {\n container: '.eJH8qe.adDDi',\n dataInitq: '[data-initq]',\n item: '.related-question-pair',\n itemDataQ: 'data-q',\n itemDataInitQ: 'data-initq',\n itemQuestionEl: '.JlqpRe',\n answerContainer: '.bCOlv, .hgKElc, .wDYxhc, .LGOjhe, .fo7IQd, .fmW3u',\n sourceTitle: 'h3',\n sourceSite: '.VuuXrf',\n sourceCite: 'cite',\n clickTarget: '.JlqpRe',\n expandedClass: 'aoRk1c',\n captchaMarker: '#captcha-form, #recaptcha, form[action*=\"/sorry/\"], .g-recaptcha, [data-sitekey]',\n} as const\n\nexport const VideoSelectors = {\n container: 'div[jscontroller=\"HWk0Gf\"]',\n sectionHeading: '.mgAbYb[role=\"heading\"]',\n item: 'a.rIRoqf',\n} as const\n\nexport const ShortVideoSelectors = {\n udm: '39',\n item: 'a.rIRoqf',\n durationPattern: /^\\d+:\\d+$/,\n platforms: ['YouTube', 'TikTok', 'Instagram', 'Facebook', 'X'],\n} as const\n\nexport const ForumSelectors = {\n section: '.ULSxyf',\n item: 'a.KYg7td.INpicf',\n title: '.hyYc0c',\n source: '.K4ETW',\n} as const\n\nexport const WhatPeopleSayingSelectors = {\n sectionTag: 'g-section-with-header',\n sectionHeadingText: 'What people are saying',\n card: '.dRzkFf[role=\"listitem\"]',\n cardLink: 'a.WlydOe[jsname=\"YKoRaf\"]',\n titleH1: 'h1.WQWxe',\n titleDiv: '.eAaXgc',\n popularCommentLabel: '.qgdis',\n source: '.sTl1Td',\n platformBadge: '.appd0, .KrMNbf',\n ytChannel: '.sjVJQd',\n ytDate: '.PLq9Je',\n authorNote: '.nDgy9d',\n} as const\n\nexport const AIOverviewSelectors = {\n root: '[data-lhcontainer][data-streaming-container][eid]',\n legacyRoot: '[data-hveid=\"CBMQAA\"]',\n wrapper: '.Fgyi2e',\n controller: '[jscontroller=\"AkrxPe\"]',\n contentSubtree: '[data-subtree=\"mfc\"]',\n header: '.heWuVc',\n heading: '.Fzsovc.cwYVJe.RJPOee',\n showMoreButton: '[aria-label=\"Show more AI Overview\"]',\n sourcesPanel: '.OZ9ddf.WAUd4',\n disclaimer: '.DuQANe.MSJHRb',\n} as const\n\nexport const AIModeSelectors = {\n root: '[data-hveid=\"CAUQAA\"]',\n wrapper: '.Fgyi2e',\n citations: '.Fgyi2e [data-hveid] a[jsname=\"pxBnId\"]',\n} as const\n\nexport const OrganicSelectors = {\n result: '.wHYlTd.tF2Cxc',\n title: 'h3.LC20lb',\n siteName: '.VuuXrf',\n cite: 'cite.tjvcx',\n snippet: '.VwiC3b',\n redditCite: 'cite.qLRx3b',\n ratingWrap: '.Y0A0hc',\n ratingValue: '.yi40Hd',\n reviewCount: '.RDApEe',\n} as const\n\nexport const LocalPackSelectors = {\n headingText: 'Businesses',\n card: '.w7Dbne',\n name: '.OSrXXb',\n ratingValue: '.yi40Hd',\n reviewCount: '.RDApEe',\n} as const\n\nexport const SELECTORS_VERSION = '2026-05-07'\n\nexport const MapsSelectors = {\n ratingAndCount: 'div.F7nice',\n hoursTable: 'table.eK4R0e',\n reviewScrollPane: 'div.m6QErb[tabindex=\"-1\"]',\n reviewScrollPaneFallback: '[role=\"main\"] div[tabindex=\"-1\"]',\n reviewCardAuthor: 'div.d4r55, span.d4r55, span.RPZfBb',\n reviewCardDate: 'span.rsqaWe',\n reviewCardText: 'span.wiI7pd',\n reviewCardOwnerBlock: 'div.CDe7pd',\n hoursTableAlt: 'table[aria-label*=\"Hour\"]',\n reviewCard: '[data-review-id]',\n reviewStars: '[role=\"img\"][aria-label*=\"star\"]',\n reviewTab: 'button[role=\"tab\"][aria-label*=\"Review\"]',\n aboutTab: 'button[role=\"tab\"][aria-label*=\"About\"]',\n expandReview: '[data-review-id] button[aria-label*=\"See more\"], [data-review-id] button.w8nwRe',\n} as const\n\nexport const MAPS_SELECTORS_VERSION = '2026-05-23'\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\n/**\n * Google Maps sometimes finishes navigation without rendering either result\n * cards or its explicit no-results message. Treating that page as a genuine\n * empty search loses data, so callers should retry it with a fresh browser (and\n * a fresh proxy when location rotation is enabled).\n */\nexport class MapsSearchIncompletePageError extends Error {\n readonly name = 'MapsSearchIncompletePageError'\n\n constructor(public readonly pageState: string) {\n super(`Google Maps search did not reach a complete result state (${pageState})`)\n }\n}\n\nexport class RequestAbortedError extends Error {\n readonly name = 'RequestAbortedError'\n constructor(message = 'Request aborted before harvest completed') {\n super(message)\n }\n}\n\nexport class LocationMismatchError extends Error {\n readonly name = 'LocationMismatchError'\n constructor(message = 'Google returned results for a different location than requested') {\n super(message)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport { getDb } from './db.js'\nimport { currentCostContext } from './cost-context.js'\nimport { kernelCostUsd, vendorCostUsd } from './cost-rates.js'\n\nlet _ready = false\nlet _migrationPromise: Promise<void> | null = null\n\nexport async function migrateCostTelemetry(): Promise<void> {\n if (_ready) return\n if (_migrationPromise) return _migrationPromise\n _migrationPromise = runCostTelemetryMigration().finally(() => {\n _migrationPromise = null\n })\n return _migrationPromise\n}\n\nasync function runCostTelemetryMigration(): Promise<void> {\n const db = getDb()\n const existing = await db.execute(`\n SELECT\n (SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('kernel_session_log', 'vendor_usage_log', 'cost_probe_runs')) = 3\n AND (SELECT COUNT(*) FROM pragma_table_info('kernel_session_log') WHERE name IN ('proxy_source', 'proxy_type', 'method')) = 3\n AND (SELECT COUNT(*) FROM pragma_table_info('vendor_usage_log') WHERE name = 'method') = 1\n AND (SELECT COUNT(*) FROM pragma_table_info('cost_probe_runs') WHERE name IN ('units', 'unit_type', 'mode')) = 3\n AS ready\n `)\n if (Number(existing.rows[0]?.ready ?? 0) === 1) {\n _ready = true\n return\n }\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kernel_session_log (\n id TEXT PRIMARY KEY,\n kernel_session_id TEXT,\n op TEXT,\n source TEXT NOT NULL,\n probe_run_id TEXT,\n user_id INTEGER,\n stealth INTEGER,\n headless_sent INTEGER,\n proxy_used INTEGER,\n fallback INTEGER NOT NULL DEFAULT 0,\n opened_at TEXT,\n closed_at TEXT,\n duration_ms INTEGER NOT NULL DEFAULT 0,\n est_cost_usd_headless REAL NOT NULL DEFAULT 0,\n est_cost_usd_headful REAL NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`)\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN method TEXT`) } catch { void 0 }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS vendor_usage_log (\n id TEXT PRIMARY KEY,\n op TEXT,\n probe_run_id TEXT,\n user_id INTEGER,\n vendor TEXT NOT NULL,\n model TEXT,\n units REAL NOT NULL DEFAULT 0,\n unit_type TEXT,\n est_cost_usd REAL NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`)\n try { await db.execute(`ALTER TABLE vendor_usage_log ADD COLUMN method TEXT`) } catch { void 0 }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS cost_probe_runs (\n id TEXT PRIMARY KEY,\n tool TEXT NOT NULL,\n target TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n wall_ms INTEGER NOT NULL DEFAULT 0,\n success INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n http_only INTEGER NOT NULL DEFAULT 0,\n kernel_used INTEGER NOT NULL DEFAULT 0,\n kernel_sessions INTEGER NOT NULL DEFAULT 0,\n kernel_seconds_total REAL NOT NULL DEFAULT 0,\n kernel_tier_observed TEXT,\n est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,\n est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,\n vendor_cost_usd REAL NOT NULL DEFAULT 0,\n charged_credits REAL NOT NULL DEFAULT 0,\n margin_usd_headless_starter REAL NOT NULL DEFAULT 0,\n margin_usd_headful_starter REAL NOT NULL DEFAULT 0,\n notes TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`)\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`) } catch { void 0 }\n _ready = true\n}\n\nexport interface KernelSessionRecord {\n kernelSessionId?: string | null\n source: string\n stealth?: boolean | null\n headlessSent?: boolean | null\n proxyUsed?: boolean | null\n proxySource?: string | null\n proxyType?: string | null\n fallback?: boolean\n openedAtMs: number\n closedAtMs: number\n error?: string | null\n}\n\nexport async function recordKernelSession(r: KernelSessionRecord): Promise<void> {\n try {\n await migrateCostTelemetry()\n const ctx = currentCostContext()\n const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs)\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO kernel_session_log\n (id, kernel_session_id, op, source, probe_run_id, user_id, stealth, headless_sent, proxy_used, proxy_source, proxy_type, fallback, opened_at, closed_at, duration_ms, est_cost_usd_headless, est_cost_usd_headful, error, method)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n randomUUID(),\n r.kernelSessionId ?? null,\n ctx?.op ?? null,\n r.source,\n ctx?.probeRunId ?? null,\n ctx?.userId ?? null,\n boolToInt(r.stealth),\n boolToInt(r.headlessSent),\n boolToInt(r.proxyUsed),\n r.proxySource ?? null,\n r.proxyType ?? null,\n r.fallback ? 1 : 0,\n new Date(r.openedAtMs).toISOString(),\n new Date(r.closedAtMs).toISOString(),\n durationMs,\n kernelCostUsd(durationMs, false),\n kernelCostUsd(durationMs, true),\n r.error ?? null,\n ctx?.subOp ?? null,\n ],\n })\n } catch (err) {\n console.warn('[cost-telemetry] recordKernelSession failed:', err instanceof Error ? err.message : String(err))\n }\n}\n\nexport interface VendorUsageRecord {\n vendor: string\n model?: string | null\n units: number\n unitType: string\n error?: string | null\n}\n\nexport async function recordVendorUsage(r: VendorUsageRecord): Promise<void> {\n try {\n await migrateCostTelemetry()\n const ctx = currentCostContext()\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO vendor_usage_log\n (id, op, probe_run_id, user_id, vendor, model, units, unit_type, est_cost_usd, error, method)\n VALUES (?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n randomUUID(),\n ctx?.op ?? null,\n ctx?.probeRunId ?? null,\n ctx?.userId ?? null,\n r.vendor,\n r.model ?? null,\n r.units,\n r.unitType,\n vendorCostUsd(r.vendor, r.units),\n r.error ?? null,\n ctx?.subOp ?? null,\n ],\n })\n } catch (err) {\n console.warn('[cost-telemetry] recordVendorUsage failed:', err instanceof Error ? err.message : String(err))\n }\n}\n\nexport interface ProbeRunRecord {\n id: string\n tool: string\n target?: string | null\n wallMs: number\n success: boolean\n error?: string | null\n httpOnly: boolean\n kernelUsed: boolean\n kernelSessions: number\n kernelSecondsTotal: number\n kernelTierObserved?: string | null\n estKernelCostUsdHeadless: number\n estKernelCostUsdHeadful: number\n vendorCostUsd: number\n chargedCredits: number\n marginUsdHeadlessStarter: number\n marginUsdHeadfulStarter: number\n units?: number | null\n unitType?: string | null\n mode?: string | null\n notes?: string | null\n}\n\nexport async function recordProbeRun(r: ProbeRunRecord): Promise<void> {\n await migrateCostTelemetry()\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO cost_probe_runs\n (id, tool, target, wall_ms, success, error, http_only, kernel_used, kernel_sessions, kernel_seconds_total, kernel_tier_observed, est_kernel_cost_usd_headless, est_kernel_cost_usd_headful, vendor_cost_usd, charged_credits, margin_usd_headless_starter, margin_usd_headful_starter, units, unit_type, mode, notes)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n r.id, r.tool, r.target ?? null, r.wallMs, r.success ? 1 : 0, r.error ?? null,\n r.httpOnly ? 1 : 0, r.kernelUsed ? 1 : 0, r.kernelSessions, r.kernelSecondsTotal,\n r.kernelTierObserved ?? null, r.estKernelCostUsdHeadless, r.estKernelCostUsdHeadful,\n r.vendorCostUsd, r.chargedCredits, r.marginUsdHeadlessStarter, r.marginUsdHeadfulStarter,\n r.units ?? null, r.unitType ?? null, r.mode ?? null, r.notes ?? null,\n ],\n })\n}\n\nfunction boolToInt(v: boolean | null | undefined): number | null {\n if (v === null || v === undefined) return null\n return v ? 1 : 0\n}\n","import { createClient, type Client } from '@libsql/client/http'\nimport { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'node:crypto'\nimport { z } from 'zod'\nimport { CREDIT_LOTS_EPOCH } from './credit-lot-integrity.js'\n\nexport { CREDIT_LOTS_EPOCH } from './credit-lot-integrity.js'\n\nconst DB_URL = process.env.TURSO_DATABASE_URL ?? 'file:./paa-api.db'\nconst DB_TOKEN = process.env.TURSO_AUTH_TOKEN\n\nlet _db: Client | null = null\nlet _rateLimitSchemaReady = false\n\nexport const CORE_SCHEMA_VERSION = '2026-07-28.5'\nconst CORE_SCHEMA_TABLES = [\n 'admin_credit_adjustment_events',\n 'admin_credit_adjustments',\n 'billing_authorizations',\n 'billing_credits',\n 'billing_debits',\n 'billing_debit_allocations',\n 'billing_events',\n 'browser_profile_labels',\n 'connected_account_billing',\n 'concurrency_locks',\n 'credit_lot_expirations',\n 'credit_lots',\n 'credit_lot_repairs',\n 'free_credit_refreshes',\n 'harvest_attempts',\n 'inbox_messages',\n 'jobs',\n 'kpo_jobs',\n 'kpo_phase_log',\n 'ledger',\n 'oauth_clients',\n 'oauth_codes',\n 'oauth_tokens',\n 'page_snapshots',\n 'password_reset_tokens',\n 'rate_limits',\n 'request_events',\n 'site_audit_jobs',\n 'site_audit_phase_log',\n 'site_extract_jobs',\n 'site_extract_pages',\n 'stripe_events',\n 'users',\n 'workflow_artifacts',\n 'workflow_runs',\n 'workflow_schedules',\n 'workflow_webhook_deliveries',\n 'workshop_registrations',\n] as const\n\nconst CORE_SCHEMA_INDEXES = [\n 'admin_credit_adjustment_events_reference',\n 'admin_credit_adjustments_user_created',\n 'billing_authorizations_user_status',\n 'billing_credits_user_status',\n 'billing_debits_user_status',\n 'billing_events_authorization',\n 'billing_events_user_created_at',\n 'concurrency_locks_operation',\n 'concurrency_locks_user_status',\n 'credit_lots_user_active',\n 'credit_lot_repairs_user_applied',\n 'harvest_attempts_job_id',\n 'harvest_attempts_user_created_at',\n 'inbox_messages_user_sent_at',\n 'jobs_created_at_status_user',\n 'jobs_status',\n 'jobs_user_created_at_status',\n 'jobs_user_id',\n 'kpo_jobs_status',\n 'kpo_jobs_user_id',\n 'kpo_phase_log_job_id',\n 'ledger_stripe_pi_unique',\n 'ledger_user_id',\n 'oauth_tokens_identity',\n 'page_snapshots_user_checked_at',\n 'prt_user_id',\n 'request_events_user_created_at',\n 'site_audit_jobs_status',\n 'site_audit_jobs_user_id',\n 'site_audit_phase_log_job_id',\n 'site_extract_jobs_status',\n 'site_extract_jobs_idempotency',\n 'site_extract_jobs_user_id',\n 'site_extract_pages_job_id',\n 'workflow_artifacts_run',\n 'workflow_runs_idempotency',\n 'workflow_runs_schedule',\n 'workflow_runs_user_created',\n 'workflow_schedules_due',\n 'workflow_schedules_user_status',\n 'workflow_webhook_deliveries_run',\n 'workshop_registrations_event_status',\n] as const\n\nconst CORE_SCHEMA_TRIGGERS = [\n 'credit_lots_bounds_insert',\n 'credit_lots_bounds_update',\n 'credit_lots_one_migration_insert',\n 'credit_lots_one_migration_update',\n] as const\n\nconst CORE_SCHEMA_COLUMNS = {\n credit_lots: ['never_expires'],\n harvest_attempts: ['debug_json'],\n oauth_tokens: ['replaced_by', 'rotated_at'],\n site_extract_jobs: [\n 'attempted_urls', 'successful_urls', 'failed_urls', 'idempotency_key', 'request_fingerprint',\n 'dispatch_attempts', 'next_dispatch_at', 'dispatch_error',\n 'settlement_attempts', 'next_settlement_at', 'settlement_error',\n ],\n stripe_events: ['attempt_count', 'claim_token', 'last_error', 'status', 'updated_at'],\n users: [\n 'balance_mc',\n 'concurrency_stripe_sub_id',\n 'extra_concurrency_slots',\n 'key_active',\n 'memory_identity',\n 'memory_key',\n 'memory_plan',\n 'memory_provisioned',\n 'memory_subscription_id',\n 'password_hash',\n 'stripe_customer_id',\n 'subscription_concurrency',\n 'subscription_id',\n 'subscription_tier',\n ],\n workshop_registrations: ['field_notes_email_id', 'reminder_48h_email_id', 'reminder_24h_email_id', 'reminder_1h_email_id'],\n workflow_artifacts: ['content'],\n workflow_runs: ['step_index', 'step_log_json', 'step_state_json', 'total_steps', 'webhook_url'],\n} as const\n\nfunction sqlStringList(values: readonly string[]): string {\n return values.map(value => `'${value.replaceAll(\"'\", \"''\")}'`).join(', ')\n}\n\nasync function hasCurrentCoreSchema(db: Client): Promise<boolean> {\n const checks = [\n `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN (${sqlStringList(CORE_SCHEMA_TABLES)})) = ${CORE_SCHEMA_TABLES.length}`,\n `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name IN (${sqlStringList(CORE_SCHEMA_INDEXES)})) = ${CORE_SCHEMA_INDEXES.length}`,\n `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'trigger' AND name IN (${sqlStringList(CORE_SCHEMA_TRIGGERS)})) = ${CORE_SCHEMA_TRIGGERS.length}`,\n ...Object.entries(CORE_SCHEMA_COLUMNS).map(([table, columns]) =>\n `(SELECT COUNT(*) FROM pragma_table_info('${table}') WHERE name IN (${sqlStringList(columns)})) = ${columns.length}`,\n ),\n ]\n const result = await db.execute(`SELECT ${checks.join(' AND ')} AS ready`)\n return Number(result.rows[0]?.ready ?? 0) === 1\n}\n\n/**\n * Data repairs that must run even when every structural schema check already\n * passes. Keeping them outside the DDL path means a crash after ALTER TABLE but\n * before the repair cannot cause the repair to be skipped forever.\n */\nasync function runIdempotentCoreDataBackfills(db: Client): Promise<void> {\n await db.execute(`\n UPDATE site_extract_jobs SET\n attempted_urls = done_urls,\n successful_urls = (\n SELECT COUNT(*) FROM site_extract_pages\n WHERE job_id = site_extract_jobs.id\n AND CASE\n WHEN json_valid(page) = 0 THEN 0\n WHEN json_extract(page, '$.extractionStatus') IS NOT NULL\n THEN json_extract(page, '$.extractionStatus') = 'successful'\n ELSE COALESCE(\n json_extract(page, '$.status') >= 200\n AND json_extract(page, '$.status') < 300\n AND json_extract(page, '$.wordCount') > 0,\n 0\n )\n END\n ),\n failed_urls = MAX(0, done_urls - (\n SELECT COUNT(*) FROM site_extract_pages\n WHERE job_id = site_extract_jobs.id\n AND CASE\n WHEN json_valid(page) = 0 THEN 0\n WHEN json_extract(page, '$.extractionStatus') IS NOT NULL\n THEN json_extract(page, '$.extractionStatus') = 'successful'\n ELSE COALESCE(\n json_extract(page, '$.status') >= 200\n AND json_extract(page, '$.status') < 300\n AND json_extract(page, '$.wordCount') > 0,\n 0\n )\n END\n ))\n WHERE done_urls > 0\n AND attempted_urls = 0\n AND successful_urls = 0\n AND failed_urls = 0\n `)\n}\n\nexport function getDb(): Client {\n if (!_db) _db = createClient({ url: DB_URL, authToken: DB_TOKEN })\n return _db\n}\n\nexport function setDbForTesting(client: Client | null): void {\n _db = client\n}\n\nexport async function migrate(): Promise<void> {\n const db = getDb()\n let applied\n try {\n applied = await db.execute({\n sql: 'SELECT 1 AS applied FROM schema_migrations WHERE version = ? LIMIT 1',\n args: [CORE_SCHEMA_VERSION],\n })\n } catch (error) {\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()\n if (!message.includes('no such table') || !message.includes('schema_migrations')) throw error\n await db.execute(`\n CREATE TABLE IF NOT EXISTS schema_migrations (\n version TEXT PRIMARY KEY,\n applied_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n applied = { rows: [] }\n }\n if (applied.rows.length > 0 && await hasCurrentCoreSchema(db)) {\n await runIdempotentCoreDataBackfills(db)\n return\n }\n\n // Existing production databases predate the migration registry. Verify the\n // complete legacy schema in one query and register it without replaying 84\n // remote DDL statements on every serverless cold start.\n if (await hasCurrentCoreSchema(db)) {\n await runIdempotentCoreDataBackfills(db)\n await db.execute({\n sql: 'INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)',\n args: [CORE_SCHEMA_VERSION],\n })\n return\n }\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n email TEXT UNIQUE NOT NULL,\n name TEXT,\n api_key TEXT UNIQUE NOT NULL,\n password_hash TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n active INTEGER NOT NULL DEFAULT 1\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n status TEXT NOT NULL DEFAULT 'pending',\n query TEXT NOT NULL,\n options TEXT NOT NULL,\n callback_url TEXT,\n result TEXT,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n started_at TEXT,\n completed_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS jobs_user_id ON jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS jobs_status ON jobs(status)`)\n await db.execute(\n `CREATE INDEX IF NOT EXISTS jobs_created_at_status_user ON jobs(created_at, status, user_id)`,\n )\n await db.execute(\n `CREATE INDEX IF NOT EXISTS jobs_user_created_at_status ON jobs(user_id, created_at, status)`,\n )\n await db.execute(`\n CREATE TABLE IF NOT EXISTS harvest_attempts (\n id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL REFERENCES jobs(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n attempt_number INTEGER NOT NULL,\n max_attempts INTEGER NOT NULL,\n query TEXT NOT NULL,\n location TEXT,\n max_questions INTEGER NOT NULL,\n status TEXT NOT NULL,\n outcome TEXT,\n kernel_session_id TEXT,\n question_count INTEGER,\n duration_ms INTEGER,\n error TEXT,\n will_retry INTEGER,\n kernel_delete_started INTEGER NOT NULL DEFAULT 0,\n kernel_delete_succeeded INTEGER,\n kernel_delete_error TEXT,\n browser_close_succeeded INTEGER,\n browser_close_error TEXT,\n debug_json TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT,\n UNIQUE(job_id, attempt_number)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS harvest_attempts_job_id ON harvest_attempts(job_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS harvest_attempts_user_created_at ON harvest_attempts(user_id, created_at DESC)`)\n try { await db.execute(`ALTER TABLE harvest_attempts ADD COLUMN debug_json TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN password_hash TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN key_active INTEGER NOT NULL DEFAULT 1`) } catch {}\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kpo_jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n status TEXT NOT NULL,\n entity_name TEXT NOT NULL,\n entity_type TEXT NOT NULL,\n url TEXT,\n request TEXT NOT NULL,\n result TEXT,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kpo_phase_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT NOT NULL REFERENCES kpo_jobs(id),\n phase TEXT NOT NULL,\n output TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS kpo_jobs_user_id ON kpo_jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kpo_jobs_status ON kpo_jobs(status)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kpo_phase_log_job_id ON kpo_phase_log(job_id)`)\n try { await db.execute(`ALTER TABLE users ADD COLUMN stripe_customer_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN balance_mc INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN extra_concurrency_slots INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN concurrency_stripe_sub_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN subscription_concurrency INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN subscription_tier TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN subscription_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_key TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_identity TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_plan TEXT NOT NULL DEFAULT 'free'`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_subscription_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_provisioned INTEGER NOT NULL DEFAULT 0`) } catch {}\n await db.execute(`\n CREATE TABLE IF NOT EXISTS concurrency_locks (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n operation TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'active',\n acquired_at TEXT NOT NULL DEFAULT (datetime('now')),\n expires_at TEXT NOT NULL,\n released_at TEXT,\n metadata TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_user_status ON concurrency_locks(user_id, status, expires_at)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_operation ON concurrency_locks(operation, status, expires_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS ledger (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n operation TEXT NOT NULL,\n description TEXT,\n stripe_pi TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS ledger_user_id ON ledger(user_id)`)\n await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS ledger_stripe_pi_unique ON ledger(stripe_pi) WHERE stripe_pi IS NOT NULL`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS credit_lots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n remaining_mc INTEGER NOT NULL,\n source TEXT NOT NULL,\n stripe_pi TEXT,\n granted_at TEXT NOT NULL DEFAULT (datetime('now')),\n expires_at TEXT NOT NULL,\n never_expires INTEGER NOT NULL DEFAULT 0\n )\n `)\n try { await db.execute(`ALTER TABLE credit_lots ADD COLUMN never_expires INTEGER NOT NULL DEFAULT 0`) } catch {}\n await db.execute(`CREATE INDEX IF NOT EXISTS credit_lots_user_active ON credit_lots(user_id, expires_at, remaining_mc)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS credit_lot_expirations (\n lot_id INTEGER PRIMARY KEY REFERENCES credit_lots(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n claim_token TEXT NOT NULL UNIQUE,\n expired_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_bounds_insert\n BEFORE INSERT ON credit_lots\n WHEN NEW.amount_mc < 0\n OR NEW.remaining_mc < 0\n OR NEW.remaining_mc > NEW.amount_mc\n BEGIN\n SELECT RAISE(ABORT, 'invalid credit lot bounds');\n END\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_bounds_update\n BEFORE UPDATE OF amount_mc, remaining_mc ON credit_lots\n WHEN NEW.amount_mc < 0\n OR NEW.remaining_mc < 0\n OR NEW.remaining_mc > NEW.amount_mc\n BEGIN\n SELECT RAISE(ABORT, 'invalid credit lot bounds');\n END\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_one_migration_insert\n BEFORE INSERT ON credit_lots\n WHEN NEW.source = 'migration'\n AND EXISTS (\n SELECT 1 FROM credit_lots\n WHERE user_id = NEW.user_id AND source = 'migration'\n )\n BEGIN\n SELECT RAISE(ABORT, 'duplicate migration lot');\n END\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_one_migration_update\n BEFORE UPDATE OF source, user_id ON credit_lots\n WHEN NEW.source = 'migration'\n AND EXISTS (\n SELECT 1 FROM credit_lots\n WHERE user_id = NEW.user_id\n AND source = 'migration'\n AND id != OLD.id\n )\n BEGIN\n SELECT RAISE(ABORT, 'duplicate migration lot');\n END\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS credit_lot_repairs (\n repair_id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n report_sha256 TEXT NOT NULL,\n classification TEXT NOT NULL,\n before_json TEXT NOT NULL,\n plan_json TEXT NOT NULL,\n after_json TEXT,\n status TEXT NOT NULL DEFAULT 'claimed',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n applied_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS credit_lot_repairs_user_applied ON credit_lot_repairs(user_id, applied_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS free_credit_refreshes (\n user_id INTEGER NOT NULL REFERENCES users(id),\n month TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (user_id, month)\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_authorizations (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n idempotency_key TEXT NOT NULL UNIQUE,\n billing_class TEXT NOT NULL,\n status TEXT NOT NULL,\n minimum_amount_mc INTEGER NOT NULL,\n maximum_amount_mc INTEGER NOT NULL,\n token_hash TEXT NOT NULL,\n metadata TEXT,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n settled_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_authorizations_user_status ON billing_authorizations(user_id, status, expires_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_events (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n authorization_id TEXT REFERENCES billing_authorizations(id),\n idempotency_key TEXT NOT NULL UNIQUE,\n billing_class TEXT NOT NULL,\n source_surface TEXT NOT NULL,\n status TEXT NOT NULL,\n amount_mc INTEGER NOT NULL DEFAULT 0,\n raw_cost_usd_nanos INTEGER,\n multiplier_bps INTEGER,\n input_tokens INTEGER,\n output_tokens INTEGER,\n cached_tokens INTEGER,\n model_steps INTEGER,\n metadata TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n settled_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_events_user_created_at ON billing_events(user_id, created_at DESC)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_events_authorization ON billing_events(authorization_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_debits (\n idempotency_key TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n operation TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'claimed',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n applied_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_debits_user_status ON billing_debits(user_id, status)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_debit_allocations (\n debit_idempotency_key TEXT NOT NULL REFERENCES billing_debits(idempotency_key),\n lot_id INTEGER NOT NULL REFERENCES credit_lots(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (debit_idempotency_key, lot_id)\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS connected_account_billing (\n user_id INTEGER PRIMARY KEY REFERENCES users(id),\n stripe_subscription_id TEXT,\n stripe_subscription_item_id TEXT,\n price_id TEXT,\n quantity INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL DEFAULT 'pending',\n last_error_code TEXT,\n checked_at TEXT NOT NULL DEFAULT (datetime('now')),\n synced_at TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_credits (\n idempotency_key TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n operation TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'claimed',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n applied_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_credits_user_status ON billing_credits(user_id, status)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS admin_credit_adjustments (\n reference TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n direction TEXT NOT NULL,\n amount_mc INTEGER NOT NULL,\n reason TEXT NOT NULL,\n actor TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'applied',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS admin_credit_adjustments_user_created ON admin_credit_adjustments(user_id, created_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS admin_credit_adjustment_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n reference TEXT NOT NULL REFERENCES admin_credit_adjustments(reference),\n seq INTEGER NOT NULL,\n kind TEXT NOT NULL,\n from_amount_mc INTEGER,\n to_amount_mc INTEGER,\n from_reason TEXT,\n to_reason TEXT,\n moved_mc INTEGER NOT NULL DEFAULT 0,\n actor TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE (reference, seq)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS admin_credit_adjustment_events_reference ON admin_credit_adjustment_events(reference)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_audit_jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER,\n status TEXT DEFAULT 'pending',\n client_domain TEXT,\n session_path TEXT,\n request TEXT,\n result TEXT,\n error TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_audit_phase_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT REFERENCES site_audit_jobs(id),\n phase TEXT,\n completed_at TEXT DEFAULT (datetime('now')),\n output_summary TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_audit_jobs_user_id ON site_audit_jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_audit_jobs_status ON site_audit_jobs(status)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_audit_phase_log_job_id ON site_audit_phase_log(job_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_extract_jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER,\n status TEXT DEFAULT 'pending',\n start_url TEXT,\n options TEXT,\n total_urls INTEGER DEFAULT 0,\n done_urls INTEGER DEFAULT 0,\n attempted_urls INTEGER DEFAULT 0,\n successful_urls INTEGER DEFAULT 0,\n failed_urls INTEGER DEFAULT 0,\n idempotency_key TEXT,\n request_fingerprint TEXT,\n dispatch_attempts INTEGER DEFAULT 0,\n next_dispatch_at TEXT,\n dispatch_error TEXT,\n settlement_attempts INTEGER DEFAULT 0,\n next_settlement_at TEXT,\n settlement_error TEXT,\n artifacts TEXT,\n error TEXT,\n billed_mc INTEGER,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_extract_pages (\n job_id TEXT,\n url TEXT,\n page TEXT,\n PRIMARY KEY (job_id, url)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_jobs_user_id ON site_extract_jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_jobs_status ON site_extract_jobs(status)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_pages_job_id ON site_extract_pages(job_id)`)\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN attempted_urls INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN successful_urls INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN failed_urls INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN idempotency_key TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN request_fingerprint TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN dispatch_attempts INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN next_dispatch_at TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN dispatch_error TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN settlement_attempts INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN next_settlement_at TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN settlement_error TEXT`) } catch {}\n await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS site_extract_jobs_idempotency ON site_extract_jobs(user_id, idempotency_key) WHERE idempotency_key IS NOT NULL`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS password_reset_tokens (\n token TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n expires_at TEXT NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS prt_user_id ON password_reset_tokens(user_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n scope TEXT NOT NULL,\n key_hash TEXT NOT NULL,\n window_start INTEGER NOT NULL,\n count INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (scope, key_hash, window_start)\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS request_events (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n source TEXT NOT NULL,\n status TEXT NOT NULL,\n query TEXT NOT NULL,\n location TEXT,\n result_count INTEGER,\n result TEXT,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS request_events_user_created_at ON request_events(user_id, created_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_schedules (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n workflow_id TEXT NOT NULL,\n name TEXT NOT NULL,\n input_json TEXT NOT NULL,\n cadence TEXT NOT NULL,\n cron_expr TEXT,\n timezone TEXT NOT NULL DEFAULT 'UTC',\n webhook_url TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n next_run_at TEXT,\n last_run_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_schedules_user_status ON workflow_schedules(user_id, status, created_at DESC)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_schedules_due ON workflow_schedules(status, next_run_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_runs (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n schedule_id TEXT,\n workflow_id TEXT NOT NULL,\n input_json TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'queued',\n manifest_json TEXT,\n error_message TEXT,\n queued_at TEXT NOT NULL DEFAULT (datetime('now')),\n started_at TEXT,\n completed_at TEXT,\n idempotency_key TEXT,\n step_index INTEGER NOT NULL DEFAULT 0,\n total_steps INTEGER,\n step_state_json TEXT,\n step_log_json TEXT,\n webhook_url TEXT\n )\n `)\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN step_index INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN total_steps INTEGER`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN step_state_json TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN step_log_json TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN webhook_url TEXT`) } catch {}\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_runs_user_created ON workflow_runs(user_id, queued_at DESC)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_runs_schedule ON workflow_runs(schedule_id, queued_at DESC)`)\n await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS workflow_runs_idempotency ON workflow_runs(idempotency_key) WHERE idempotency_key IS NOT NULL`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_artifacts (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES workflow_runs(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n kind TEXT NOT NULL,\n label TEXT NOT NULL,\n path TEXT NOT NULL,\n content_type TEXT NOT NULL,\n bytes INTEGER,\n rows_count INTEGER,\n content TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n try { await db.execute(`ALTER TABLE workflow_artifacts ADD COLUMN content TEXT`) } catch {}\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_artifacts_run ON workflow_artifacts(run_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_webhook_deliveries (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES workflow_runs(id),\n schedule_id TEXT,\n user_id INTEGER NOT NULL REFERENCES users(id),\n webhook_url TEXT NOT NULL,\n status TEXT NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n delivered_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_webhook_deliveries_run ON workflow_webhook_deliveries(run_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS stripe_events (\n event_id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'processed',\n attempt_count INTEGER NOT NULL DEFAULT 1,\n claim_token TEXT,\n last_error TEXT,\n processed_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n for (const col of [\n `status TEXT NOT NULL DEFAULT 'processed'`,\n 'attempt_count INTEGER NOT NULL DEFAULT 1',\n 'claim_token TEXT',\n 'last_error TEXT',\n 'updated_at TEXT',\n ]) {\n try {\n await db.execute(`ALTER TABLE stripe_events ADD COLUMN ${col}`)\n } catch {\n void 0\n }\n }\n await db.execute(`\n UPDATE stripe_events\n SET updated_at = COALESCE(updated_at, processed_at),\n status = COALESCE(status, 'processed'),\n attempt_count = MAX(1, COALESCE(attempt_count, 1))\n WHERE updated_at IS NULL\n OR status IS NULL\n OR attempt_count IS NULL\n OR attempt_count < 1\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS oauth_clients (\n client_id TEXT PRIMARY KEY,\n redirect_uris TEXT NOT NULL,\n name TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS oauth_codes (\n code TEXT PRIMARY KEY,\n client_id TEXT NOT NULL,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n identity TEXT NOT NULL,\n scope TEXT NOT NULL,\n resource TEXT,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS oauth_tokens (\n refresh_token TEXT PRIMARY KEY,\n client_id TEXT NOT NULL,\n identity TEXT NOT NULL,\n scope TEXT NOT NULL,\n resource TEXT,\n expires_at TEXT NOT NULL,\n revoked INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS oauth_tokens_identity ON oauth_tokens(identity)`)\n for (const col of ['replaced_by TEXT', 'rotated_at TEXT']) {\n try {\n await db.execute(`ALTER TABLE oauth_tokens ADD COLUMN ${col}`)\n } catch {\n void 0\n }\n }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS page_snapshots (\n user_id INTEGER NOT NULL REFERENCES users(id),\n url TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n content TEXT NOT NULL,\n title TEXT,\n content_bytes INTEGER NOT NULL,\n truncated INTEGER NOT NULL DEFAULT 0,\n checked_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (user_id, url)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS page_snapshots_user_checked_at ON page_snapshots(user_id, checked_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS inbox_messages (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n subject TEXT NOT NULL,\n body_md TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'operator',\n sent_at TEXT NOT NULL DEFAULT (datetime('now')),\n opened_at TEXT,\n deleted_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS inbox_messages_user_sent_at ON inbox_messages(user_id, sent_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workshop_registrations (\n id TEXT PRIMARY KEY,\n event_slug TEXT NOT NULL,\n email TEXT NOT NULL,\n first_name TEXT NOT NULL,\n last_name TEXT,\n status TEXT NOT NULL,\n zoom_registrant_id TEXT,\n zoom_join_url TEXT,\n resend_contact_id TEXT,\n confirmation_email_id TEXT,\n field_notes_email_id TEXT,\n reminder_48h_email_id TEXT,\n reminder_24h_email_id TEXT,\n reminder_1h_email_id TEXT,\n last_error TEXT,\n registered_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(event_slug, email)\n )\n `)\n for (const col of ['field_notes_email_id TEXT', 'reminder_48h_email_id TEXT', 'reminder_24h_email_id TEXT', 'reminder_1h_email_id TEXT']) {\n try {\n await db.execute(`ALTER TABLE workshop_registrations ADD COLUMN ${col}`)\n } catch {\n void 0\n }\n }\n await db.execute(`\n CREATE INDEX IF NOT EXISTS workshop_registrations_event_status\n ON workshop_registrations(event_slug, status, registered_at)\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS browser_profile_labels (\n user_id INTEGER NOT NULL REFERENCES users(id),\n profile TEXT NOT NULL,\n label TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT,\n PRIMARY KEY (user_id, profile)\n )\n `)\n if (!(await hasCurrentCoreSchema(db))) {\n throw new Error('core schema migration finished without all required tables, indexes, and columns')\n }\n await runIdempotentCoreDataBackfills(db)\n await db.execute({\n sql: 'INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)',\n args: [CORE_SCHEMA_VERSION],\n })\n}\n\nexport interface OAuthClient {\n client_id: string\n redirect_uris: string[]\n name: string | null\n created_at: string\n}\n\nexport async function registerClient(clientId: string, redirectUris: string[], name: string | null): Promise<void> {\n await getDb().execute({\n sql: 'INSERT INTO oauth_clients (client_id, redirect_uris, name) VALUES (?, ?, ?)',\n args: [clientId, JSON.stringify(redirectUris), name],\n })\n}\n\nexport async function getClient(clientId: string): Promise<OAuthClient | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM oauth_clients WHERE client_id = ?', args: [clientId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return undefined\n let uris: string[] = []\n try {\n const parsed = JSON.parse(String(row.redirect_uris))\n if (Array.isArray(parsed)) uris = parsed.filter((u): u is string => typeof u === 'string')\n } catch {\n uris = []\n }\n return {\n client_id: String(row.client_id),\n redirect_uris: uris,\n name: row.name != null ? String(row.name) : null,\n created_at: String(row.created_at),\n }\n}\n\nexport interface OAuthCode {\n code: string\n client_id: string\n redirect_uri: string\n code_challenge: string\n identity: string\n scope: string\n resource: string | null\n expires_at: string\n}\n\nexport async function putCode(code: OAuthCode): Promise<void> {\n await getDb().execute({\n sql: 'INSERT INTO oauth_codes (code, client_id, redirect_uri, code_challenge, identity, scope, resource, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',\n args: [code.code, code.client_id, code.redirect_uri, code.code_challenge, code.identity, code.scope, code.resource, code.expires_at],\n })\n}\n\nexport async function consumeCode(code: string): Promise<OAuthCode | undefined> {\n const db = getDb()\n const res = await db.execute({ sql: 'SELECT * FROM oauth_codes WHERE code = ?', args: [code] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n await db.execute({ sql: 'DELETE FROM oauth_codes WHERE code = ?', args: [code] })\n if (!row) return undefined\n return {\n code: String(row.code),\n client_id: String(row.client_id),\n redirect_uri: String(row.redirect_uri),\n code_challenge: String(row.code_challenge),\n identity: String(row.identity),\n scope: String(row.scope),\n resource: row.resource != null ? String(row.resource) : null,\n expires_at: String(row.expires_at),\n }\n}\n\nexport interface OAuthRefresh {\n refresh_token: string\n client_id: string\n identity: string\n scope: string\n resource: string | null\n expires_at: string\n revoked: number\n replaced_by?: string | null\n rotated_at?: string | null\n}\n\nexport async function putRefresh(token: OAuthRefresh): Promise<void> {\n await getDb().execute({\n sql: 'INSERT INTO oauth_tokens (refresh_token, client_id, identity, scope, resource, expires_at, revoked) VALUES (?, ?, ?, ?, ?, ?, 0)',\n args: [token.refresh_token, token.client_id, token.identity, token.scope, token.resource, token.expires_at],\n })\n}\n\nexport async function getRefresh(refreshToken: string): Promise<OAuthRefresh | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM oauth_tokens WHERE refresh_token = ?', args: [refreshToken] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return undefined\n return {\n refresh_token: String(row.refresh_token),\n client_id: String(row.client_id),\n identity: String(row.identity),\n scope: String(row.scope),\n resource: row.resource != null ? String(row.resource) : null,\n expires_at: String(row.expires_at),\n revoked: Number(row.revoked ?? 0),\n replaced_by: row.replaced_by != null ? String(row.replaced_by) : null,\n rotated_at: row.rotated_at != null ? String(row.rotated_at) : null,\n }\n}\n\nexport async function revokeRefresh(refreshToken: string): Promise<void> {\n await getDb().execute({ sql: 'UPDATE oauth_tokens SET revoked = 1 WHERE refresh_token = ?', args: [refreshToken] })\n}\n\nexport async function rotateRefresh(oldToken: string, next: OAuthRefresh): Promise<void> {\n await putRefresh(next)\n await getDb().execute({\n sql: 'UPDATE oauth_tokens SET revoked = 1, replaced_by = ?, rotated_at = ? WHERE refresh_token = ?',\n args: [next.refresh_token, new Date().toISOString(), oldToken],\n })\n}\n\nexport type StripeEventClaim =\n | { state: 'claimed'; token: string }\n | { state: 'processed' | 'processing' }\n\n/**\n * Claims one Stripe event for processing.\n *\n * Failed work is immediately retryable. A processing claim can be taken over\n * only after ten minutes, beyond the serverless request ceiling, and the token\n * prevents an older worker from completing or failing a newer claim.\n */\nexport async function claimStripeEvent(eventId: string): Promise<StripeEventClaim> {\n const token = randomUUID()\n const claimed = await getDb().execute({\n sql: `INSERT INTO stripe_events\n (event_id, status, attempt_count, claim_token, last_error, processed_at, updated_at)\n VALUES (?, 'processing', 1, ?, NULL, datetime('now'), datetime('now'))\n ON CONFLICT(event_id) DO UPDATE SET\n status = 'processing',\n attempt_count = stripe_events.attempt_count + 1,\n claim_token = excluded.claim_token,\n last_error = NULL,\n updated_at = datetime('now')\n WHERE stripe_events.status = 'failed'\n OR (\n stripe_events.status = 'processing'\n AND stripe_events.updated_at <= datetime('now', '-10 minutes')\n )`,\n args: [eventId, token],\n })\n if (claimed.rowsAffected > 0) return { state: 'claimed', token }\n\n const current = await getDb().execute({\n sql: 'SELECT status FROM stripe_events WHERE event_id = ? LIMIT 1',\n args: [eventId],\n })\n return { state: String(current.rows[0]?.status) === 'processed' ? 'processed' : 'processing' }\n}\n\nexport async function completeStripeEvent(eventId: string, token: string): Promise<boolean> {\n const result = await getDb().execute({\n sql: `UPDATE stripe_events\n SET status = 'processed',\n claim_token = NULL,\n last_error = NULL,\n processed_at = datetime('now'),\n updated_at = datetime('now')\n WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,\n args: [eventId, token],\n })\n return result.rowsAffected === 1\n}\n\nexport async function failStripeEvent(eventId: string, token: string, error: string): Promise<boolean> {\n const result = await getDb().execute({\n sql: `UPDATE stripe_events\n SET status = 'failed',\n claim_token = NULL,\n last_error = ?,\n updated_at = datetime('now')\n WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,\n args: [error.slice(0, 500), eventId, token],\n })\n return result.rowsAffected === 1\n}\n\nexport async function stripeEventAlreadyProcessed(eventId: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: `SELECT 1 FROM stripe_events WHERE event_id = ? AND status = 'processed' LIMIT 1`,\n args: [eventId],\n })\n return res.rows.length > 0\n}\n\nexport function generateApiKey(): string {\n return 'sk_' + randomBytes(24).toString('hex')\n}\n\nexport interface User {\n id: number\n email: string\n name: string | null\n api_key: string\n key_active: number\n password_hash: string | null\n created_at: string\n active: number\n stripe_customer_id: string | null\n balance_mc: number\n extra_concurrency_slots: number\n concurrency_stripe_sub_id: string | null\n subscription_concurrency: number\n subscription_tier: string | null\n subscription_id: string | null\n memory_plan: string\n memory_subscription_id: string | null\n memory_provisioned: number\n}\n\nexport function hashPassword(password: string): string {\n const salt = randomBytes(16).toString('hex')\n const hash = scryptSync(password, salt, 64)\n return `${salt}:${hash.toString('hex')}`\n}\n\nexport function verifyPassword(password: string, stored: string): boolean {\n const [salt, hash] = stored.split(':')\n const hashBuf = Buffer.from(hash, 'hex')\n const derived = scryptSync(password, salt, 64)\n return timingSafeEqual(hashBuf, derived)\n}\n\nfunction rowToUser(row: Record<string, unknown>): User {\n return {\n id: Number(row.id),\n email: String(row.email),\n name: row.name != null ? String(row.name) : null,\n api_key: String(row.api_key),\n key_active: Number(row.key_active ?? 1),\n password_hash: row.password_hash != null ? String(row.password_hash) : null,\n created_at: String(row.created_at),\n active: Number(row.active),\n stripe_customer_id: row.stripe_customer_id != null ? String(row.stripe_customer_id) : null,\n balance_mc: Number(row.balance_mc ?? 0),\n extra_concurrency_slots: Number(row.extra_concurrency_slots ?? 0),\n concurrency_stripe_sub_id: row.concurrency_stripe_sub_id != null ? String(row.concurrency_stripe_sub_id) : null,\n subscription_concurrency: Number(row.subscription_concurrency ?? 0),\n subscription_tier: row.subscription_tier != null ? String(row.subscription_tier) : null,\n subscription_id: row.subscription_id != null ? String(row.subscription_id) : null,\n memory_plan: row.memory_plan != null ? String(row.memory_plan) : 'free',\n memory_subscription_id: row.memory_subscription_id != null ? String(row.memory_subscription_id) : null,\n memory_provisioned: Number(row.memory_provisioned ?? 0),\n }\n}\n\nexport async function getUserByEmail(email: string): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE email = ? AND active = 1', args: [email] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function getUserByApiKey(api_key: string): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE api_key = ? AND active = 1 AND key_active = 1', args: [api_key] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function getUserById(id: number | bigint): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE id = ? AND active = 1', args: [id] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function getUserMemoryCreds(id: number | bigint): Promise<{ memory_key: string | null; memory_identity: string | null }> {\n const res = await getDb().execute({ sql: 'SELECT memory_key, memory_identity FROM users WHERE id = ?', args: [id] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return {\n memory_key: row?.memory_key != null ? String(row.memory_key) : null,\n memory_identity: row?.memory_identity != null ? String(row.memory_identity) : null,\n }\n}\n\nexport async function setUserMemoryCreds(id: number | bigint, memoryKeyEncrypted: string, memoryIdentity: string): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET memory_key = ?, memory_identity = ? WHERE id = ?', args: [memoryKeyEncrypted, memoryIdentity, id] })\n}\n\nexport async function setPassword(id: number | bigint, password: string): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET password_hash = ? WHERE id = ?', args: [hashPassword(password), id] })\n}\n\nexport async function getUserStats(userId: number | bigint): Promise<{ total: number; done: number; failed: number; totalQuestions: number; lastUsed: string | null }> {\n const db = getDb()\n const [totR, doneR, failR, tqR, luR] = await Promise.all([\n db.execute({ sql: 'SELECT COUNT(*) as n FROM jobs WHERE user_id = ?', args: [userId] }),\n db.execute({ sql: \"SELECT COUNT(*) as n FROM jobs WHERE user_id = ? AND status = 'done'\", args: [userId] }),\n db.execute({ sql: \"SELECT COUNT(*) as n FROM jobs WHERE user_id = ? AND status = 'failed'\", args: [userId] }),\n db.execute({ sql: \"SELECT COALESCE(SUM(json_extract(result, '$.totalQuestions')), 0) as n FROM jobs WHERE user_id = ? AND status = 'done'\", args: [userId] }),\n db.execute({ sql: 'SELECT MAX(created_at) as t FROM jobs WHERE user_id = ?', args: [userId] }),\n ])\n return {\n total: Number(totR.rows[0]?.n ?? 0),\n done: Number(doneR.rows[0]?.n ?? 0),\n failed: Number(failR.rows[0]?.n ?? 0),\n totalQuestions: Number(tqR.rows[0]?.n ?? 0),\n lastUsed: luR.rows[0]?.t != null ? String(luR.rows[0].t) : null,\n }\n}\n\nexport async function createUser(email: string, name?: string, password?: string, stripeCustomerId?: string): Promise<{ id: number | bigint; email: string; name?: string; api_key: string; password?: string }> {\n const db = getDb()\n const api_key = generateApiKey()\n const plainPassword = password ?? randomBytes(6).toString('hex')\n const password_hash = hashPassword(plainPassword)\n const result = await db.execute({\n sql: 'INSERT INTO users (email, name, api_key, password_hash, stripe_customer_id) VALUES (?, ?, ?, ?, ?)',\n args: [email, name ?? null, api_key, password_hash, stripeCustomerId ?? null],\n })\n return { id: result.lastInsertRowid!, email, name, api_key, password: plainPassword }\n}\n\nexport async function checkRateLimit(\n scope: string,\n key: string,\n limit: number,\n windowSeconds: number,\n): Promise<{ allowed: boolean; count: number; remaining: number; resetSeconds: number }> {\n const nowSeconds = Math.floor(Date.now() / 1000)\n const windowStart = Math.floor(nowSeconds / windowSeconds) * windowSeconds\n const keyHash = createHash('sha256').update(key).digest('hex')\n const db = getDb()\n\n if (!_rateLimitSchemaReady) {\n await db.execute(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n scope TEXT NOT NULL,\n key_hash TEXT NOT NULL,\n window_start INTEGER NOT NULL,\n count INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (scope, key_hash, window_start)\n )\n `)\n _rateLimitSchemaReady = true\n }\n\n await db.execute({\n sql: `\n INSERT INTO rate_limits (scope, key_hash, window_start, count)\n VALUES (?, ?, ?, 1)\n ON CONFLICT(scope, key_hash, window_start)\n DO UPDATE SET count = count + 1, updated_at = datetime('now')\n `,\n args: [scope, keyHash, windowStart],\n })\n\n const res = await db.execute({\n sql: 'SELECT count FROM rate_limits WHERE scope = ? AND key_hash = ? AND window_start = ?',\n args: [scope, keyHash, windowStart],\n })\n const count = Number(res.rows[0]?.count ?? 1)\n return {\n allowed: count <= limit,\n count,\n remaining: Math.max(0, limit - count),\n resetSeconds: Math.max(1, windowStart + windowSeconds - nowSeconds),\n }\n}\n\nexport async function createPasswordResetToken(userId: number | bigint): Promise<string> {\n const token = randomBytes(32).toString('hex')\n const tokenHash = createHash('sha256').update(token).digest('hex')\n const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString()\n await getDb().execute({\n sql: 'INSERT INTO password_reset_tokens (token, user_id, expires_at) VALUES (?, ?, ?)',\n args: [tokenHash, userId, expiresAt],\n })\n return token\n}\n\nexport async function consumePasswordResetToken(token: string): Promise<number | null> {\n const db = getDb()\n const tokenHash = createHash('sha256').update(token).digest('hex')\n const res = await db.execute({\n sql: \"SELECT user_id, expires_at, used FROM password_reset_tokens WHERE token = ?\",\n args: [tokenHash],\n })\n const row = res.rows[0]\n if (!row) return null\n if (Number(row.used)) return null\n if (new Date(String(row.expires_at)) < new Date()) return null\n await db.execute({ sql: 'UPDATE password_reset_tokens SET used = 1 WHERE token = ?', args: [tokenHash] })\n return Number(row.user_id)\n}\n\nexport async function rotateApiKey(userId: number | bigint): Promise<string> {\n const newKey = generateApiKey()\n await getDb().execute({ sql: 'UPDATE users SET api_key = ?, key_active = 1 WHERE id = ?', args: [newKey, userId] })\n return newKey\n}\n\nexport async function revokeApiKey(userId: number | bigint): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET key_active = 0 WHERE id = ?', args: [userId] })\n}\n\nexport async function listUsers(): Promise<Omit<User, 'api_key'>[]> {\n const res = await getDb().execute('SELECT id, email, name, created_at, active FROM users ORDER BY created_at DESC')\n return res.rows.map(r => rowToUser({ ...r as unknown as Record<string, unknown>, api_key: '', key_active: 1 }))\n}\n\nexport async function deactivateUser(id: number): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET active = 0 WHERE id = ?', args: [id] })\n}\n\ninterface RawJob {\n id: string\n user_id: number\n status: string\n query: string\n options: string\n callback_url: string | null\n result: string | null\n error: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n}\n\nexport interface Job {\n id: string\n user_id: number\n status: string\n query: string\n options: Record<string, unknown>\n callback_url: string | null\n result: unknown | null\n error: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n}\n\nexport interface HarvestAttemptLog {\n id: string\n job_id: string\n user_id: number\n attempt_number: number\n max_attempts: number\n query: string\n location: string | null\n max_questions: number\n status: string\n outcome: string | null\n kernel_session_id: string | null\n question_count: number | null\n duration_ms: number | null\n error: string | null\n will_retry: boolean | null\n kernel_delete_started: boolean\n kernel_delete_succeeded: boolean | null\n kernel_delete_error: string | null\n browser_close_succeeded: boolean | null\n browser_close_error: string | null\n debug: unknown | null\n created_at: string\n completed_at: string | null\n}\n\nexport interface StartHarvestAttemptInput {\n jobId: string\n userId: number | bigint\n attemptNumber: number\n maxAttempts: number\n query: string\n location: string | null\n maxQuestions: number\n startedAt: string\n}\n\nexport interface FinishHarvestAttemptInput {\n jobId: string\n attemptNumber: number\n outcome: string\n kernelSessionId: string | null\n questionCount: number\n durationMs: number\n error: string | null\n willRetry: boolean\n kernelDeleteStarted: boolean\n kernelDeleteSucceeded: boolean | null\n kernelDeleteError: string | null\n browserCloseSucceeded: boolean | null\n browserCloseError: string | null\n debug: unknown | null\n completedAt: string\n}\n\nfunction rowToRawJob(row: Record<string, unknown>): RawJob {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n status: String(row.status),\n query: String(row.query),\n options: String(row.options),\n callback_url: row.callback_url != null ? String(row.callback_url) : null,\n result: row.result != null ? String(row.result) : null,\n error: row.error != null ? String(row.error) : null,\n created_at: String(row.created_at),\n started_at: row.started_at != null ? String(row.started_at) : null,\n completed_at: row.completed_at != null ? String(row.completed_at) : null,\n }\n}\n\nfunction nullableBoolean(value: unknown): boolean | null {\n return value == null ? null : Number(value) === 1\n}\n\nfunction parseNullableJson(value: unknown): unknown | null {\n if (value == null) return null\n try {\n return JSON.parse(String(value)) as unknown\n } catch {\n return null\n }\n}\n\nfunction rowToHarvestAttemptLog(row: Record<string, unknown>): HarvestAttemptLog {\n return {\n id: String(row.id),\n job_id: String(row.job_id),\n user_id: Number(row.user_id),\n attempt_number: Number(row.attempt_number),\n max_attempts: Number(row.max_attempts),\n query: String(row.query),\n location: row.location != null ? String(row.location) : null,\n max_questions: Number(row.max_questions),\n status: String(row.status),\n outcome: row.outcome != null ? String(row.outcome) : null,\n kernel_session_id: row.kernel_session_id != null ? String(row.kernel_session_id) : null,\n question_count: row.question_count != null ? Number(row.question_count) : null,\n duration_ms: row.duration_ms != null ? Number(row.duration_ms) : null,\n error: row.error != null ? String(row.error) : null,\n will_retry: nullableBoolean(row.will_retry),\n kernel_delete_started: Number(row.kernel_delete_started ?? 0) === 1,\n kernel_delete_succeeded: nullableBoolean(row.kernel_delete_succeeded),\n kernel_delete_error: row.kernel_delete_error != null ? String(row.kernel_delete_error) : null,\n browser_close_succeeded: nullableBoolean(row.browser_close_succeeded),\n browser_close_error: row.browser_close_error != null ? String(row.browser_close_error) : null,\n debug: parseNullableJson(row.debug_json),\n created_at: String(row.created_at),\n completed_at: row.completed_at != null ? String(row.completed_at) : null,\n }\n}\n\nfunction deserialize(raw: RawJob): Job {\n return {\n ...raw,\n options: JSON.parse(raw.options) as Record<string, unknown>,\n result: raw.result ? JSON.parse(raw.result) : null,\n }\n}\n\nexport async function createJob(userId: number | bigint, query: string, options: object, callbackUrl?: string): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: 'INSERT INTO jobs (id, user_id, query, options, callback_url) VALUES (?, ?, ?, ?, ?)',\n args: [id, userId, query, JSON.stringify(options), callbackUrl ?? null],\n })\n return id\n}\n\nexport async function createRunningJob(userId: number | bigint, query: string, options: object): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: `INSERT INTO jobs (id, user_id, query, options, status, started_at) VALUES (?, ?, ?, ?, 'running', datetime('now'))`,\n args: [id, userId, query, JSON.stringify(options)],\n })\n return id\n}\n\nexport async function startHarvestAttempt(input: StartHarvestAttemptInput): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO harvest_attempts (\n id, job_id, user_id, attempt_number, max_attempts, query, location, max_questions, status, created_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'running', ?)\n ON CONFLICT(job_id, attempt_number) DO UPDATE SET\n status = 'running',\n created_at = excluded.created_at,\n completed_at = NULL,\n outcome = NULL,\n error = NULL,\n debug_json = NULL\n `,\n args: [\n randomUUID(),\n input.jobId,\n input.userId,\n input.attemptNumber,\n input.maxAttempts,\n input.query,\n input.location,\n input.maxQuestions,\n input.startedAt,\n ],\n })\n}\n\nexport async function finishHarvestAttempt(input: FinishHarvestAttemptInput): Promise<void> {\n await getDb().execute({\n sql: `\n UPDATE harvest_attempts SET\n status = 'finished',\n outcome = ?,\n kernel_session_id = ?,\n question_count = ?,\n duration_ms = ?,\n error = ?,\n will_retry = ?,\n kernel_delete_started = ?,\n kernel_delete_succeeded = ?,\n kernel_delete_error = ?,\n browser_close_succeeded = ?,\n browser_close_error = ?,\n debug_json = ?,\n completed_at = ?\n WHERE job_id = ? AND attempt_number = ?\n `,\n args: [\n input.outcome,\n input.kernelSessionId,\n input.questionCount,\n input.durationMs,\n input.error,\n input.willRetry ? 1 : 0,\n input.kernelDeleteStarted ? 1 : 0,\n input.kernelDeleteSucceeded == null ? null : input.kernelDeleteSucceeded ? 1 : 0,\n input.kernelDeleteError,\n input.browserCloseSucceeded == null ? null : input.browserCloseSucceeded ? 1 : 0,\n input.browserCloseError,\n input.debug == null ? null : JSON.stringify(input.debug),\n input.completedAt,\n input.jobId,\n input.attemptNumber,\n ],\n })\n}\n\nexport async function listHarvestAttempts(jobId: string, userId?: number | bigint): Promise<HarvestAttemptLog[]> {\n const res = userId == null\n ? await getDb().execute({\n sql: `SELECT * FROM harvest_attempts WHERE job_id = ? ORDER BY attempt_number ASC`,\n args: [jobId],\n })\n : await getDb().execute({\n sql: `\n SELECT ha.*\n FROM harvest_attempts ha\n INNER JOIN jobs j ON j.id = ha.job_id\n WHERE ha.job_id = ? AND j.user_id = ?\n ORDER BY ha.attempt_number ASC\n `,\n args: [jobId, userId],\n })\n return res.rows.map(r => rowToHarvestAttemptLog(r as unknown as Record<string, unknown>))\n}\n\nexport async function getJob(id: string, userId?: number | bigint): Promise<Job | undefined> {\n const db = getDb()\n const res = userId\n ? await db.execute({ sql: 'SELECT * FROM jobs WHERE id = ? AND user_id = ?', args: [id, userId] })\n : await db.execute({ sql: 'SELECT * FROM jobs WHERE id = ?', args: [id] })\n return res.rows[0] ? deserialize(rowToRawJob(res.rows[0] as unknown as Record<string, unknown>)) : undefined\n}\n\nexport async function listJobs(userId: number | bigint): Promise<Job[]> {\n const res = await getDb().execute({ sql: 'SELECT * FROM jobs WHERE user_id = ? ORDER BY created_at DESC LIMIT 50', args: [userId] })\n return res.rows.map(r => deserialize(rowToRawJob(r as unknown as Record<string, unknown>)))\n}\n\nexport interface PageSnapshotRow {\n contentHash: string\n content: string\n title: string | null\n contentBytes: number\n truncated: boolean\n checkedAt: string\n}\n\nexport async function getPageSnapshot(userId: number | bigint, url: string): Promise<PageSnapshotRow | null> {\n const res = await getDb().execute({\n sql: 'SELECT content_hash, content, title, content_bytes, truncated, checked_at FROM page_snapshots WHERE user_id = ? AND url = ?',\n args: [userId, url],\n })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return null\n return {\n contentHash: String(row.content_hash),\n content: String(row.content),\n title: row.title == null ? null : String(row.title),\n contentBytes: Number(row.content_bytes),\n truncated: Number(row.truncated) === 1,\n checkedAt: String(row.checked_at),\n }\n}\n\nexport interface UpsertPageSnapshotInput {\n contentHash: string\n content: string\n title: string | null\n contentBytes: number\n truncated: boolean\n}\n\nexport async function upsertPageSnapshot(userId: number | bigint, url: string, snapshot: UpsertPageSnapshotInput): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO page_snapshots (user_id, url, content_hash, content, title, content_bytes, truncated, checked_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(user_id, url) DO UPDATE SET\n content_hash = excluded.content_hash,\n content = excluded.content,\n title = excluded.title,\n content_bytes = excluded.content_bytes,\n truncated = excluded.truncated,\n checked_at = excluded.checked_at\n `,\n args: [userId, url, snapshot.contentHash, snapshot.content, snapshot.title, snapshot.contentBytes, snapshot.truncated ? 1 : 0],\n })\n}\n\nexport interface RequestEvent {\n id: string\n user_id: number\n source: string\n status: string\n query: string\n location: string | null\n result_count: number | null\n result: unknown | null\n error: string | null\n created_at: string\n}\n\nconst REQUEST_EVENT_RESULT_MAX_BYTES = 32 * 1024\n\nfunction serializeRequestEventResult(result: unknown): string | null {\n if (result == null) return null\n const serialized = JSON.stringify(result)\n const bytes = Buffer.byteLength(serialized, 'utf8')\n if (bytes <= REQUEST_EVENT_RESULT_MAX_BYTES) return serialized\n\n // request_events powers the lightweight history list; the full response is\n // already returned to the caller (and, for jobs/exports, stored in its\n // canonical artifact). Keeping another multi-megabyte copy here grew the\n // production database by gigabytes without any product consumer reading it.\n return JSON.stringify({\n truncated: true,\n originalBytes: bytes,\n sha256: createHash('sha256').update(serialized).digest('hex'),\n })\n}\n\nfunction rowToRequestEvent(row: Record<string, unknown>): RequestEvent {\n const rawResult = row.result != null ? String(row.result) : null\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n source: String(row.source),\n status: String(row.status),\n query: String(row.query),\n location: row.location != null ? String(row.location) : null,\n result_count: row.result_count != null ? Number(row.result_count) : null,\n result: rawResult ? JSON.parse(rawResult) : null,\n error: row.error != null ? String(row.error) : null,\n created_at: String(row.created_at),\n }\n}\n\nexport async function logRequestEvent(input: {\n userId: number | bigint\n source: string\n status: 'done' | 'failed'\n query: string\n location?: string | null\n resultCount?: number | null\n result?: unknown\n error?: string | null\n}): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: `\n INSERT INTO request_events (id, user_id, source, status, query, location, result_count, result, error)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n id,\n input.userId,\n input.source,\n input.status,\n input.query,\n input.location ?? null,\n input.resultCount ?? null,\n serializeRequestEventResult(input.result),\n input.error ?? null,\n ],\n })\n return id\n}\n\nexport async function listRequestEvents(userId: number | bigint, limit = 100): Promise<RequestEvent[]> {\n const res = await getDb().execute({\n sql: `\n SELECT id, user_id, source, status, query, location, result_count, NULL AS result, error, created_at\n FROM request_events\n WHERE user_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n `,\n args: [userId, limit],\n })\n return res.rows.map(r => rowToRequestEvent(r as unknown as Record<string, unknown>))\n}\n\nexport type WorkflowScheduleStatus = 'active' | 'paused' | 'deleted'\nexport type WorkflowCadence = 'daily' | 'weekly' | 'monthly'\nexport type WorkflowRunStatus = 'queued' | 'running' | 'succeeded' | 'partial' | 'failed' | 'cancelled'\n\nexport interface WorkflowScheduleRecord {\n id: string\n user_id: number\n workflow_id: string\n name: string\n input: Record<string, unknown>\n cadence: WorkflowCadence\n cron_expr: string | null\n timezone: string\n webhook_url: string | null\n status: WorkflowScheduleStatus\n next_run_at: string | null\n last_run_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface WorkflowRunRecord {\n id: string\n user_id: number\n schedule_id: string | null\n workflow_id: string\n input: Record<string, unknown>\n status: WorkflowRunStatus\n manifest: unknown | null\n error_message: string | null\n queued_at: string\n started_at: string | null\n completed_at: string | null\n idempotency_key: string | null\n step_index: number\n total_steps: number | null\n step_log: WorkflowStepLogEntry[]\n webhook_url: string | null\n}\n\nexport interface WorkflowStepLogEntry {\n stepIndex: number\n stepId: string\n stepTitle: string\n output: Record<string, unknown>\n warnings: string[]\n at: string\n}\n\nexport interface WorkflowArtifactRecord {\n id: string\n run_id: string\n user_id: number\n kind: string\n label: string\n path: string\n content_type: string\n bytes: number | null\n rows_count: number | null\n content: string | null\n created_at: string\n}\n\nfunction parseJsonRecordValue(value: unknown): Record<string, unknown> {\n if (value == null) return {}\n try {\n const parsed = JSON.parse(String(value)) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {}\n } catch {\n return {}\n }\n}\n\nfunction rowToWorkflowSchedule(row: Record<string, unknown>): WorkflowScheduleRecord {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n workflow_id: String(row.workflow_id),\n name: String(row.name),\n input: parseJsonRecordValue(row.input_json),\n cadence: String(row.cadence) as WorkflowCadence,\n cron_expr: row.cron_expr != null ? String(row.cron_expr) : null,\n timezone: String(row.timezone ?? 'UTC'),\n webhook_url: row.webhook_url != null ? String(row.webhook_url) : null,\n status: String(row.status) as WorkflowScheduleStatus,\n next_run_at: row.next_run_at != null ? String(row.next_run_at) : null,\n last_run_at: row.last_run_at != null ? String(row.last_run_at) : null,\n created_at: String(row.created_at),\n updated_at: String(row.updated_at),\n }\n}\n\nfunction rowToWorkflowRun(row: Record<string, unknown>): WorkflowRunRecord {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n schedule_id: row.schedule_id != null ? String(row.schedule_id) : null,\n workflow_id: String(row.workflow_id),\n input: parseJsonRecordValue(row.input_json),\n status: String(row.status) as WorkflowRunStatus,\n manifest: row.manifest_json != null ? parseNullableJson(row.manifest_json) : null,\n error_message: row.error_message != null ? String(row.error_message) : null,\n queued_at: String(row.queued_at),\n started_at: row.started_at != null ? String(row.started_at) : null,\n completed_at: row.completed_at != null ? String(row.completed_at) : null,\n idempotency_key: row.idempotency_key != null ? String(row.idempotency_key) : null,\n step_index: row.step_index != null ? Number(row.step_index) : 0,\n total_steps: row.total_steps != null ? Number(row.total_steps) : null,\n step_log: parseWorkflowStepLog(row.step_log_json),\n webhook_url: row.webhook_url != null ? String(row.webhook_url) : null,\n }\n}\n\nfunction parseWorkflowStepLog(value: unknown): WorkflowStepLogEntry[] {\n if (value == null) return []\n try {\n const parsed = JSON.parse(String(value)) as unknown\n return Array.isArray(parsed) ? parsed as WorkflowStepLogEntry[] : []\n } catch {\n return []\n }\n}\n\nfunction rowToWorkflowArtifact(row: Record<string, unknown>): WorkflowArtifactRecord {\n return {\n id: String(row.id),\n run_id: String(row.run_id),\n user_id: Number(row.user_id),\n kind: String(row.kind),\n label: String(row.label),\n path: String(row.path),\n content_type: String(row.content_type),\n bytes: row.bytes != null ? Number(row.bytes) : null,\n rows_count: row.rows_count != null ? Number(row.rows_count) : null,\n content: row.content != null ? String(row.content) : null,\n created_at: String(row.created_at),\n }\n}\n\nexport async function createWorkflowSchedule(input: {\n userId: number | bigint\n workflowId: string\n name: string\n workflowInput: Record<string, unknown>\n cadence: WorkflowCadence\n timezone?: string\n webhookUrl?: string | null\n nextRunAt?: string | null\n}): Promise<WorkflowScheduleRecord> {\n const id = randomUUID()\n await getDb().execute({\n sql: `\n INSERT INTO workflow_schedules (\n id, user_id, workflow_id, name, input_json, cadence, timezone, webhook_url, next_run_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n id,\n input.userId,\n input.workflowId,\n input.name,\n JSON.stringify(input.workflowInput),\n input.cadence,\n input.timezone ?? 'UTC',\n input.webhookUrl ?? null,\n input.nextRunAt ?? null,\n ],\n })\n const schedule = await getWorkflowSchedule(id, input.userId)\n if (!schedule) throw new Error('Failed to create workflow schedule')\n return schedule\n}\n\nexport async function getWorkflowSchedule(id: string, userId?: number | bigint): Promise<WorkflowScheduleRecord | null> {\n const res = userId == null\n ? await getDb().execute({ sql: 'SELECT * FROM workflow_schedules WHERE id = ? AND status != ? LIMIT 1', args: [id, 'deleted'] })\n : await getDb().execute({ sql: 'SELECT * FROM workflow_schedules WHERE id = ? AND user_id = ? AND status != ? LIMIT 1', args: [id, userId, 'deleted'] })\n return res.rows[0] ? rowToWorkflowSchedule(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function listWorkflowSchedules(userId: number | bigint): Promise<WorkflowScheduleRecord[]> {\n const res = await getDb().execute({\n sql: `SELECT * FROM workflow_schedules WHERE user_id = ? AND status != 'deleted' ORDER BY created_at DESC LIMIT 100`,\n args: [userId],\n })\n return res.rows.map(r => rowToWorkflowSchedule(r as unknown as Record<string, unknown>))\n}\n\nexport async function patchWorkflowSchedule(input: {\n id: string\n userId: number | bigint\n name?: string\n status?: WorkflowScheduleStatus\n workflowInput?: Record<string, unknown>\n cadence?: WorkflowCadence\n timezone?: string\n webhookUrl?: string | null\n nextRunAt?: string | null\n}): Promise<WorkflowScheduleRecord | null> {\n const sets: string[] = []\n const args: Array<string | number | bigint | null> = []\n if (input.name !== undefined) { sets.push('name = ?'); args.push(input.name) }\n if (input.status !== undefined) { sets.push('status = ?'); args.push(input.status) }\n if (input.workflowInput !== undefined) { sets.push('input_json = ?'); args.push(JSON.stringify(input.workflowInput)) }\n if (input.cadence !== undefined) { sets.push('cadence = ?'); args.push(input.cadence) }\n if (input.timezone !== undefined) { sets.push('timezone = ?'); args.push(input.timezone) }\n if (input.webhookUrl !== undefined) { sets.push('webhook_url = ?'); args.push(input.webhookUrl) }\n if (input.nextRunAt !== undefined) { sets.push('next_run_at = ?'); args.push(input.nextRunAt) }\n if (sets.length === 0) return getWorkflowSchedule(input.id, input.userId)\n sets.push(`updated_at = datetime('now')`)\n await getDb().execute({\n sql: `UPDATE workflow_schedules SET ${sets.join(', ')} WHERE id = ? AND user_id = ? AND status != 'deleted'`,\n args: [...args, input.id, input.userId],\n })\n return getWorkflowSchedule(input.id, input.userId)\n}\n\nexport async function deleteWorkflowSchedule(id: string, userId: number | bigint): Promise<boolean> {\n const res = await getDb().execute({\n sql: `UPDATE workflow_schedules SET status = 'deleted', updated_at = datetime('now') WHERE id = ? AND user_id = ? AND status != 'deleted'`,\n args: [id, userId],\n })\n return res.rowsAffected > 0\n}\n\nexport async function listDueWorkflowSchedules(nowIso: string, limit = 10): Promise<WorkflowScheduleRecord[]> {\n const res = await getDb().execute({\n sql: `\n SELECT * FROM workflow_schedules\n WHERE status = 'active' AND next_run_at IS NOT NULL AND next_run_at <= ?\n ORDER BY next_run_at ASC\n LIMIT ?\n `,\n args: [nowIso, limit],\n })\n return res.rows.map(r => rowToWorkflowSchedule(r as unknown as Record<string, unknown>))\n}\n\nexport async function markWorkflowScheduleRan(id: string, lastRunAt: string, nextRunAt: string | null): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_schedules SET last_run_at = ?, next_run_at = ?, updated_at = datetime('now') WHERE id = ?`,\n args: [lastRunAt, nextRunAt, id],\n })\n}\n\nexport async function createWorkflowRun(input: {\n id?: string\n userId: number | bigint\n workflowId: string\n workflowInput: Record<string, unknown>\n scheduleId?: string | null\n idempotencyKey?: string | null\n webhookUrl?: string | null\n totalSteps?: number | null\n}): Promise<WorkflowRunRecord> {\n const id = input.id ?? randomUUID()\n const args = [\n id,\n input.userId,\n input.scheduleId ?? null,\n input.workflowId,\n JSON.stringify(input.workflowInput),\n input.idempotencyKey ?? null,\n input.webhookUrl ?? null,\n input.totalSteps ?? null,\n ]\n try {\n await getDb().execute({\n sql: `\n INSERT INTO workflow_runs (id, user_id, schedule_id, workflow_id, input_json, idempotency_key, webhook_url, total_steps)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args,\n })\n } catch (err) {\n if (input.idempotencyKey) {\n const existing = await getWorkflowRunByIdempotencyKey(input.idempotencyKey)\n if (existing) return existing\n }\n throw err\n }\n const run = await getWorkflowRun(id, input.userId)\n if (!run) throw new Error('Failed to create workflow run')\n return run\n}\n\nexport async function getWorkflowRun(id: string, userId?: number | bigint): Promise<WorkflowRunRecord | null> {\n const res = userId == null\n ? await getDb().execute({ sql: 'SELECT * FROM workflow_runs WHERE id = ? LIMIT 1', args: [id] })\n : await getDb().execute({ sql: 'SELECT * FROM workflow_runs WHERE id = ? AND user_id = ? LIMIT 1', args: [id, userId] })\n return res.rows[0] ? rowToWorkflowRun(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function getWorkflowRunByIdempotencyKey(idempotencyKey: string): Promise<WorkflowRunRecord | null> {\n const res = await getDb().execute({ sql: 'SELECT * FROM workflow_runs WHERE idempotency_key = ? LIMIT 1', args: [idempotencyKey] })\n return res.rows[0] ? rowToWorkflowRun(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function listWorkflowRuns(userId: number | bigint, limit = 50): Promise<WorkflowRunRecord[]> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM workflow_runs WHERE user_id = ? ORDER BY queued_at DESC LIMIT ?',\n args: [userId, limit],\n })\n return res.rows.map(r => rowToWorkflowRun(r as unknown as Record<string, unknown>))\n}\n\nexport async function markWorkflowRunRunning(id: string): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = 'running', started_at = COALESCE(started_at, datetime('now')) WHERE id = ?`,\n args: [id],\n })\n}\n\nexport async function completeWorkflowRunRecord(id: string, status: Exclude<WorkflowRunStatus, 'queued' | 'running' | 'cancelled'>, manifest: unknown): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = ?, manifest_json = ?, completed_at = datetime('now') WHERE id = ?`,\n args: [status, JSON.stringify(manifest), id],\n })\n}\n\nexport async function failWorkflowRunRecord(id: string, errorMessage: string, manifest?: unknown): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = 'failed', error_message = ?, manifest_json = ?, completed_at = datetime('now') WHERE id = ?`,\n args: [errorMessage, manifest == null ? null : JSON.stringify(manifest), id],\n })\n}\n\nfunction workflowArtifactContentType(kind: string): string {\n if (kind === 'html_report') return 'text/html; charset=utf-8'\n if (kind === 'csv') return 'text/csv; charset=utf-8'\n if (kind === 'json') return 'application/json; charset=utf-8'\n return 'text/plain; charset=utf-8'\n}\n\nexport interface WorkflowArtifactInput {\n kind: string\n label: string\n path: string\n bytes?: number\n rows?: number\n content?: string | null\n}\n\nasync function insertWorkflowArtifact(runId: string, userId: number | bigint, artifact: WorkflowArtifactInput): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO workflow_artifacts (id, run_id, user_id, kind, label, path, content_type, bytes, rows_count, content)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n randomUUID(),\n runId,\n userId,\n artifact.kind,\n artifact.label,\n artifact.path,\n workflowArtifactContentType(artifact.kind),\n artifact.bytes ?? null,\n artifact.rows ?? null,\n artifact.content ?? null,\n ],\n })\n}\n\nexport async function replaceWorkflowArtifacts(runId: string, userId: number | bigint, artifacts: WorkflowArtifactInput[]): Promise<WorkflowArtifactRecord[]> {\n await getDb().execute({ sql: 'DELETE FROM workflow_artifacts WHERE run_id = ? AND user_id = ?', args: [runId, userId] })\n for (const artifact of artifacts) await insertWorkflowArtifact(runId, userId, artifact)\n return listWorkflowArtifacts(runId, userId)\n}\n\nexport async function appendWorkflowArtifacts(runId: string, userId: number | bigint, artifacts: WorkflowArtifactInput[]): Promise<WorkflowArtifactRecord[]> {\n for (const artifact of artifacts) await insertWorkflowArtifact(runId, userId, artifact)\n return listWorkflowArtifacts(runId, userId)\n}\n\nexport async function getWorkflowRunStepState(id: string, userId?: number | bigint): Promise<{ stepIndex: number; totalSteps: number | null; state: unknown; stepLog: WorkflowStepLogEntry[] } | null> {\n const res = userId == null\n ? await getDb().execute({ sql: 'SELECT step_index, total_steps, step_state_json, step_log_json FROM workflow_runs WHERE id = ? LIMIT 1', args: [id] })\n : await getDb().execute({ sql: 'SELECT step_index, total_steps, step_state_json, step_log_json FROM workflow_runs WHERE id = ? AND user_id = ? LIMIT 1', args: [id, userId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return null\n return {\n stepIndex: row.step_index != null ? Number(row.step_index) : 0,\n totalSteps: row.total_steps != null ? Number(row.total_steps) : null,\n state: row.step_state_json != null ? parseNullableJson(row.step_state_json) : null,\n stepLog: parseWorkflowStepLog(row.step_log_json),\n }\n}\n\nexport async function advanceWorkflowRunStep(id: string, input: {\n stepIndex: number\n totalSteps: number\n state: unknown\n status: WorkflowRunStatus\n logEntry: WorkflowStepLogEntry\n appendLog: WorkflowStepLogEntry[]\n}): Promise<void> {\n const stepLog = [...input.appendLog, input.logEntry]\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = ?, step_index = ?, total_steps = ?, step_state_json = ?, step_log_json = ?, started_at = COALESCE(started_at, datetime('now')) WHERE id = ?`,\n args: [input.status, input.stepIndex, input.totalSteps, JSON.stringify(input.state ?? null), JSON.stringify(stepLog), id],\n })\n}\n\nexport async function listWorkflowArtifacts(runId: string, userId: number | bigint): Promise<WorkflowArtifactRecord[]> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM workflow_artifacts WHERE run_id = ? AND user_id = ? ORDER BY created_at ASC',\n args: [runId, userId],\n })\n return res.rows.map(r => rowToWorkflowArtifact(r as unknown as Record<string, unknown>))\n}\n\nexport async function getWorkflowArtifact(id: string, userId: number | bigint): Promise<WorkflowArtifactRecord | null> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM workflow_artifacts WHERE id = ? AND user_id = ? LIMIT 1',\n args: [id, userId],\n })\n return res.rows[0] ? rowToWorkflowArtifact(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function recordWorkflowWebhookDelivery(input: {\n runId: string\n scheduleId?: string | null\n userId: number | bigint\n webhookUrl: string\n status: 'delivered' | 'failed'\n attempts: number\n lastError?: string | null\n}): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO workflow_webhook_deliveries (\n id, run_id, schedule_id, user_id, webhook_url, status, attempts, last_error, delivered_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n randomUUID(),\n input.runId,\n input.scheduleId ?? null,\n input.userId,\n input.webhookUrl,\n input.status,\n input.attempts,\n input.lastError ?? null,\n input.status === 'delivered' ? new Date().toISOString() : null,\n ],\n })\n}\n\nexport async function countActiveUsers(): Promise<number> {\n const res = await getDb().execute(`SELECT COUNT(*) as n FROM users WHERE active = 1`)\n return Number(res.rows[0]?.n ?? 0)\n}\n\nexport async function countActiveJobsForUser(userId: number | bigint): Promise<number> {\n const res = await getDb().execute({\n sql: `SELECT COUNT(*) as n FROM jobs WHERE user_id = ? AND (status = 'pending' OR (status = 'running' AND started_at > datetime('now', '-10 minutes')))`,\n args: [userId],\n })\n return Number(res.rows[0]?.n ?? 0)\n}\n\nexport async function claimPendingJob(): Promise<RawJob | undefined> {\n const db = getDb()\n const res = await db.execute(`SELECT * FROM jobs WHERE status = 'pending' ORDER BY created_at LIMIT 1`)\n const job = res.rows[0] ? rowToRawJob(res.rows[0] as unknown as Record<string, unknown>) : undefined\n if (!job) return undefined\n const upd = await db.execute({\n sql: `UPDATE jobs SET status = 'running', started_at = datetime('now') WHERE id = ? AND status = 'pending'`,\n args: [job.id],\n })\n return upd.rowsAffected === 0 ? undefined : { ...job, status: 'running' }\n}\n\nexport async function completeJob(id: string, result: object): Promise<void> {\n await getDb().execute({ sql: `UPDATE jobs SET status = 'done', result = ?, completed_at = datetime('now') WHERE id = ?`, args: [JSON.stringify(result), id] })\n}\n\nexport async function failJob(id: string, error: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE jobs SET status = 'failed', error = ?, completed_at = datetime('now') WHERE id = ?`, args: [error, id] })\n}\n\nexport async function cancelJob(id: string, reason: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE jobs SET status = 'cancelled', error = ?, completed_at = datetime('now') WHERE id = ?`, args: [reason, id] })\n}\n\nexport interface KpoJobRow {\n id: string\n user_id: number\n status: string\n entity_name: string\n entity_type: string\n url: string | null\n request: string\n result: string | null\n error: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface KpoPhaseLogRow {\n id: number\n job_id: string\n phase: string\n output: string\n created_at: string\n}\n\nfunction rowToKpoJob(row: Record<string, unknown>): KpoJobRow {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n status: String(row.status),\n entity_name: String(row.entity_name),\n entity_type: String(row.entity_type),\n url: row.url != null ? String(row.url) : null,\n request: String(row.request),\n result: row.result != null ? String(row.result) : null,\n error: row.error != null ? String(row.error) : null,\n created_at: String(row.created_at),\n updated_at: String(row.updated_at),\n }\n}\n\nfunction rowToKpoPhaseLog(row: Record<string, unknown>): KpoPhaseLogRow {\n return {\n id: Number(row.id),\n job_id: String(row.job_id),\n phase: String(row.phase),\n output: String(row.output),\n created_at: String(row.created_at),\n }\n}\n\nexport async function createKpoJob(jobId: string, userId: number, entityName: string, entityType: string, request: object, url?: string): Promise<void> {\n await getDb().execute({\n sql: `INSERT INTO kpo_jobs (id, user_id, status, entity_name, entity_type, url, request) VALUES (?, ?, 'pending', ?, ?, ?, ?)`,\n args: [jobId, userId, entityName, entityType, url ?? null, JSON.stringify(request)],\n })\n}\n\nexport async function claimPendingKpoJob(): Promise<KpoJobRow | null> {\n const db = getDb()\n const upd = await db.execute(\n `UPDATE kpo_jobs SET status='running', updated_at=datetime('now') WHERE id = (SELECT id FROM kpo_jobs WHERE status='pending' ORDER BY created_at ASC LIMIT 1) RETURNING *`\n )\n if (upd.rowsAffected === 0) return null\n return upd.rows[0] ? rowToKpoJob(upd.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function getKpoJob(jobId: string): Promise<KpoJobRow | null> {\n const res = await getDb().execute({ sql: `SELECT * FROM kpo_jobs WHERE id = ?`, args: [jobId] })\n return res.rows[0] ? rowToKpoJob(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function updateKpoJobState(jobId: string, status: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE kpo_jobs SET status = ?, updated_at = datetime('now') WHERE id = ?`, args: [status, jobId] })\n}\n\nexport async function completeKpoJob(jobId: string, result: object): Promise<void> {\n await getDb().execute({ sql: `UPDATE kpo_jobs SET status = 'done', result = ?, updated_at = datetime('now') WHERE id = ?`, args: [JSON.stringify(result), jobId] })\n}\n\nexport async function failKpoJob(jobId: string, error: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE kpo_jobs SET status = 'failed', error = ?, updated_at = datetime('now') WHERE id = ?`, args: [error, jobId] })\n}\n\nexport async function logKpoPhaseComplete(jobId: string, phase: string, output: unknown): Promise<void> {\n await getDb().execute({\n sql: `INSERT INTO kpo_phase_log (job_id, phase, output) VALUES (?, ?, ?)`,\n args: [jobId, phase, JSON.stringify(output)],\n })\n}\n\nexport async function getKpoPhaseLog(jobId: string): Promise<KpoPhaseLogRow[]> {\n const res = await getDb().execute({ sql: `SELECT * FROM kpo_phase_log WHERE job_id = ? ORDER BY created_at ASC`, args: [jobId] })\n return res.rows.map(r => rowToKpoPhaseLog(r as unknown as Record<string, unknown>))\n}\n\nexport async function listKpoJobs(userId?: number): Promise<KpoJobRow[]> {\n const res = userId != null\n ? await getDb().execute({ sql: `SELECT * FROM kpo_jobs WHERE user_id = ? ORDER BY created_at DESC LIMIT 50`, args: [userId] })\n : await getDb().execute(`SELECT * FROM kpo_jobs ORDER BY created_at DESC LIMIT 50`)\n return res.rows.map(r => rowToKpoJob(r as unknown as Record<string, unknown>))\n}\n\nexport async function getUserByStripeCustomerId(customerId: string): Promise<User | undefined> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM users WHERE stripe_customer_id = ? AND active = 1',\n args: [customerId],\n })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function setStripeCustomerId(userId: number, customerId: string): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET stripe_customer_id = ? WHERE id = ?',\n args: [customerId, userId],\n })\n}\n\nexport async function setExtraConcurrencySlots(userId: number, slots: number): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET extra_concurrency_slots = ? WHERE id = ?',\n args: [Math.max(0, slots), userId],\n })\n}\n\nexport async function setConcurrencySubId(userId: number, subId: string | null): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET concurrency_stripe_sub_id = ? WHERE id = ?',\n args: [subId, userId],\n })\n}\n\nexport async function setSubscriptionTier(userId: number, tier: string | null, concurrency: number, subId: string | null): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET subscription_tier = ?, subscription_concurrency = ?, subscription_id = ? WHERE id = ?',\n args: [tier, Math.max(0, Math.round(concurrency)), subId, userId],\n })\n}\n\nexport type ConnectedAccountBillingStatus = 'pending' | 'synced' | 'blocked' | 'error'\n\nexport interface ConnectedAccountBillingState {\n user_id: number\n stripe_subscription_id: string | null\n stripe_subscription_item_id: string | null\n price_id: string | null\n quantity: number\n status: ConnectedAccountBillingStatus\n last_error_code: string | null\n checked_at: string\n synced_at: string | null\n updated_at: string\n}\n\nfunction rowToConnectedAccountBillingState(row: Record<string, unknown>): ConnectedAccountBillingState {\n return {\n user_id: Number(row.user_id),\n stripe_subscription_id: row.stripe_subscription_id != null ? String(row.stripe_subscription_id) : null,\n stripe_subscription_item_id: row.stripe_subscription_item_id != null ? String(row.stripe_subscription_item_id) : null,\n price_id: row.price_id != null ? String(row.price_id) : null,\n quantity: Math.max(0, Number(row.quantity ?? 0)),\n status: (['pending', 'synced', 'blocked', 'error'].includes(String(row.status)) ? String(row.status) : 'error') as ConnectedAccountBillingStatus,\n last_error_code: row.last_error_code != null ? String(row.last_error_code) : null,\n checked_at: String(row.checked_at),\n synced_at: row.synced_at != null ? String(row.synced_at) : null,\n updated_at: String(row.updated_at),\n }\n}\n\nexport async function getConnectedAccountBillingState(userId: number | bigint): Promise<ConnectedAccountBillingState | null> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1',\n args: [userId],\n })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return row ? rowToConnectedAccountBillingState(row) : null\n}\n\nexport async function setConnectedAccountBillingState(input: {\n userId: number | bigint\n stripeSubscriptionId: string | null\n stripeSubscriptionItemId: string | null\n priceId: string | null\n quantity: number\n status: ConnectedAccountBillingStatus\n lastErrorCode?: string | null\n synced?: boolean\n}): Promise<ConnectedAccountBillingState> {\n const quantity = Math.max(0, Math.round(input.quantity))\n await getDb().execute({\n sql: `INSERT INTO connected_account_billing\n (user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, checked_at, synced_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))\n ON CONFLICT(user_id) DO UPDATE SET\n stripe_subscription_id = excluded.stripe_subscription_id,\n stripe_subscription_item_id = excluded.stripe_subscription_item_id,\n price_id = excluded.price_id,\n quantity = excluded.quantity,\n status = excluded.status,\n last_error_code = excluded.last_error_code,\n checked_at = datetime('now'),\n synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,\n updated_at = datetime('now')`,\n args: [\n input.userId,\n input.stripeSubscriptionId,\n input.stripeSubscriptionItemId,\n input.priceId,\n quantity,\n input.status,\n input.lastErrorCode ?? null,\n input.synced === true ? 1 : 0,\n input.synced === true ? 1 : 0,\n ],\n })\n const state = await getConnectedAccountBillingState(input.userId)\n if (!state) throw new Error('connected account billing state was not persisted')\n return state\n}\n\nexport async function listUsersDueForConnectedAccountBillingReconciliation(\n staleMinutes = 60,\n limit = 20,\n): Promise<User[]> {\n const modifier = `-${Math.max(1, Math.round(staleMinutes))} minutes`\n const res = await getDb().execute({\n sql: `SELECT u.*\n FROM users u\n LEFT JOIN connected_account_billing b ON b.user_id = u.id\n WHERE u.active = 1\n AND u.subscription_id IS NOT NULL\n AND (b.checked_at IS NULL OR b.checked_at <= datetime('now', ?))\n ORDER BY COALESCE(b.checked_at, u.created_at) ASC\n LIMIT ?`,\n args: [modifier, Math.max(1, Math.min(100, Math.round(limit)))],\n })\n return res.rows.map(row => rowToUser(row as unknown as Record<string, unknown>))\n}\n\nexport async function setMemoryPlan(userId: number, plan: 'free' | 'pro' | 'team', subscriptionId: string | null): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET memory_plan = ?, memory_subscription_id = ? WHERE id = ?',\n args: [plan, subscriptionId, userId],\n })\n}\n\nexport async function getMemoryPlan(userId: number | bigint): Promise<{ plan: string; memory_subscription_id: string | null }> {\n const res = await getDb().execute({ sql: 'SELECT memory_plan, memory_subscription_id FROM users WHERE id = ?', args: [userId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return {\n plan: row?.memory_plan != null ? String(row.memory_plan) : 'free',\n memory_subscription_id: row?.memory_subscription_id != null ? String(row.memory_subscription_id) : null,\n }\n}\n\nexport async function getMemoryProvisioned(userId: number | bigint): Promise<boolean> {\n const res = await getDb().execute({ sql: 'SELECT memory_provisioned FROM users WHERE id = ?', args: [userId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return Number(row?.memory_provisioned ?? 0) === 1\n}\n\nexport async function setMemoryProvisioned(userId: number | bigint, provisioned: boolean): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET memory_provisioned = ? WHERE id = ?', args: [provisioned ? 1 : 0, userId] })\n}\n\nexport async function resolveUserByMemorySubscription(subscriptionId: string): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE memory_subscription_id = ? AND active = 1', args: [subscriptionId] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport const CREDIT_LOT_TTL = '+3 months'\n\nasync function ensureMigrated(userId: number): Promise<number> {\n const db = getDb()\n const inserted = await db.execute({\n sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, expires_at)\n SELECT ?, legacy.balance_mc, legacy.balance_mc, 'migration', datetime('now', ?)\n FROM (\n SELECT COALESCE(SUM(amount_mc), 0) AS balance_mc\n FROM ledger\n WHERE user_id = ?\n ) AS legacy\n WHERE legacy.balance_mc > 0\n AND EXISTS (\n SELECT 1 FROM ledger\n WHERE user_id = ? AND created_at < ?\n )\n AND NOT EXISTS (\n SELECT 1 FROM credit_lots WHERE user_id = ?\n )`,\n args: [userId, CREDIT_LOT_TTL, userId, userId, CREDIT_LOTS_EPOCH, userId],\n })\n return inserted.rowsAffected\n}\n\nexport async function expireOldLots(): Promise<{ expired_lots: number; expired_mc: number }> {\n const db = getDb()\n const lots = await db.execute({\n sql: `SELECT id\n FROM credit_lots\n WHERE remaining_mc > 0\n AND never_expires = 0\n AND expires_at <= datetime('now')\n LIMIT 500`,\n })\n if (lots.rows.length === 0) return { expired_lots: 0, expired_mc: 0 }\n const claims: Array<{ token: string; insertResultIndex: number }> = []\n const stmts: Array<{ sql: string; args: Array<string | number> }> = []\n for (const lot of lots.rows) {\n const lotId = Number(lot.id)\n const token = randomUUID()\n claims.push({ token, insertResultIndex: stmts.length })\n stmts.push({\n sql: `INSERT OR IGNORE INTO credit_lot_expirations (lot_id, user_id, amount_mc, claim_token)\n SELECT id, user_id, remaining_mc, ?\n FROM credit_lots\n WHERE id = ?\n AND remaining_mc > 0\n AND never_expires = 0\n AND expires_at <= datetime('now')`,\n args: [token, lotId],\n })\n stmts.push({\n sql: `UPDATE credit_lots\n SET remaining_mc = 0\n WHERE id = ?\n AND EXISTS (\n SELECT 1 FROM credit_lot_expirations\n WHERE lot_id = ? AND claim_token = ?\n )`,\n args: [lotId, lotId, token],\n })\n stmts.push({\n sql: `INSERT INTO ledger (user_id, amount_mc, operation, description)\n SELECT user_id, -amount_mc, 'credit_expired', 'credits expired'\n FROM credit_lot_expirations\n WHERE lot_id = ? AND claim_token = ?`,\n args: [lotId, token],\n })\n }\n const results = await db.batch(stmts, 'write')\n const claimedTokens = claims\n .filter(claim => results[claim.insertResultIndex]?.rowsAffected === 1)\n .map(claim => claim.token)\n if (claimedTokens.length === 0) return { expired_lots: 0, expired_mc: 0 }\n\n const placeholders = claimedTokens.map(() => '?').join(', ')\n const expired = await db.execute({\n sql: `SELECT user_id, amount_mc\n FROM credit_lot_expirations\n WHERE claim_token IN (${placeholders})`,\n args: claimedTokens,\n })\n const users = new Set(expired.rows.map(row => Number(row.user_id)))\n for (const uid of users) await reconcileBalanceMc(uid)\n return {\n expired_lots: expired.rows.length,\n expired_mc: expired.rows.reduce((sum, row) => sum + Number(row.amount_mc), 0),\n }\n}\n\nexport async function migrateExistingBalancesToLots(): Promise<{ migrated: number; total_mc: number }> {\n const db = getDb()\n const res = await db.execute({\n sql: `SELECT DISTINCT u.id AS user_id\n FROM users u\n JOIN ledger l ON l.user_id = u.id\n WHERE l.created_at < ?\n AND NOT EXISTS (SELECT 1 FROM credit_lots WHERE user_id = u.id)`,\n args: [CREDIT_LOTS_EPOCH],\n })\n let migrated = 0\n let totalMc = 0\n for (const row of res.rows) {\n const uid = Number(row.user_id)\n const inserted = await ensureMigrated(uid)\n if (inserted === 0) continue\n const lot = await db.execute({\n sql: `SELECT amount_mc FROM credit_lots\n WHERE user_id = ? AND source = 'migration'\n ORDER BY id DESC LIMIT 1`,\n args: [uid],\n })\n const amountMc = Number(lot.rows[0]?.amount_mc ?? 0)\n await reconcileBalanceMc(uid)\n migrated += 1\n totalMc += amountMc\n }\n return { migrated, total_mc: totalMc }\n}\n\nexport async function creditMc(\n userId: number,\n mc: number,\n operation: string,\n description?: string,\n stripePaymentIntent?: string,\n options?: { neverExpires?: boolean },\n): Promise<number> {\n const db = getDb()\n await ensureMigrated(userId)\n let neverExpires = options?.neverExpires\n if (neverExpires === undefined && mc > 0) {\n const tier = await db.execute({ sql: 'SELECT subscription_tier FROM users WHERE id = ? LIMIT 1', args: [userId] })\n neverExpires = String(tier.rows[0]?.subscription_tier ?? '').startsWith('sonic')\n }\n const expiry = neverExpires ? '9999-12-31 23:59:59' : null\n\n if (stripePaymentIntent) {\n const stmts: Array<{ sql: string; args: Array<string | number | null> }> = [{\n sql: 'INSERT OR IGNORE INTO ledger (user_id, amount_mc, operation, description, stripe_pi) VALUES (?, ?, ?, ?, ?)',\n args: [userId, mc, operation, description ?? null, stripePaymentIntent],\n }]\n if (mc > 0) {\n stmts.push({\n sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires)\n SELECT ?, ?, ?, ?, ?, COALESCE(?, datetime('now', ?)), ?\n WHERE changes() = 1`,\n args: [userId, mc, mc, operation, stripePaymentIntent, expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0],\n })\n }\n stmts.push({\n sql: 'UPDATE users SET balance_mc = balance_mc + ? WHERE id = ? AND changes() = 1',\n args: [mc, userId],\n })\n await db.batch(stmts, 'write')\n const res = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return Number(res.rows[0]?.balance_mc ?? 0)\n }\n\n const stmts: Array<{ sql: string; args: Array<string | number | null> }> = []\n if (mc > 0) {\n stmts.push({ sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires) VALUES (?, ?, ?, ?, ?, COALESCE(?, datetime('now', ?)), ?)`, args: [userId, mc, mc, operation, null, expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0] })\n }\n stmts.push({ sql: 'UPDATE users SET balance_mc = balance_mc + ? WHERE id = ?', args: [mc, userId] })\n stmts.push({ sql: 'INSERT INTO ledger (user_id, amount_mc, operation, description, stripe_pi) VALUES (?, ?, ?, ?, ?)', args: [userId, mc, operation, description ?? null, null] })\n await db.batch(stmts, 'write')\n const res = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return Number(res.rows[0]?.balance_mc ?? 0)\n}\n\nexport async function creditMcIdempotent(\n userId: number,\n mc: number,\n operation: string,\n description: string,\n idempotencyKey: string,\n options?: { neverExpires?: boolean },\n): Promise<{ ok: boolean; balance_mc: number; duplicate: boolean }> {\n if (!Number.isSafeInteger(mc) || mc <= 0) throw new Error('idempotent credit amount must be a positive integer')\n if (!idempotencyKey || idempotencyKey.length > 500) throw new Error('idempotent credit key is required and must be bounded')\n\n const db = getDb()\n await ensureMigrated(userId)\n\n let neverExpires = options?.neverExpires\n if (neverExpires === undefined) {\n const tier = await db.execute({ sql: 'SELECT subscription_tier FROM users WHERE id = ? LIMIT 1', args: [userId] })\n neverExpires = String(tier.rows[0]?.subscription_tier ?? '').startsWith('sonic')\n }\n const expiry = neverExpires ? '9999-12-31 23:59:59' : null\n\n const readBalance = async (): Promise<number> => {\n const res = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return Number(res.rows[0]?.balance_mc ?? 0)\n }\n\n let results\n try {\n results = await db.batch([\n {\n sql: \"INSERT OR IGNORE INTO billing_credits (idempotency_key, user_id, amount_mc, operation, description, status) VALUES (?, ?, ?, ?, ?, 'claimed')\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: \"UPDATE billing_credits SET status = 'applying' WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed'\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires)\n SELECT user_id, amount_mc, amount_mc, operation, NULL, COALESCE(?, datetime('now', ?)), ?\n FROM billing_credits\n WHERE idempotency_key = ? AND status = 'applying'`,\n args: [expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0, idempotencyKey],\n },\n {\n sql: `UPDATE users\n SET balance_mc = balance_mc + COALESCE((\n SELECT amount_mc FROM billing_credits WHERE idempotency_key = ? AND status = 'applying'\n ), 0)\n WHERE id = ?`,\n args: [idempotencyKey, userId],\n },\n {\n sql: \"INSERT INTO ledger (user_id, amount_mc, operation, description) SELECT user_id, amount_mc, operation, description FROM billing_credits WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n {\n sql: \"UPDATE billing_credits SET status = 'applied', applied_at = datetime('now') WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n ], 'write')\n } catch (error) {\n try {\n const recovered = await db.execute({\n sql: \"SELECT user_id, amount_mc, operation, description FROM billing_credits WHERE idempotency_key = ? AND status = 'applied' LIMIT 1\",\n args: [idempotencyKey],\n })\n const row = recovered.rows[0]\n if (\n row\n && Number(row.user_id) === userId\n && Number(row.amount_mc) === mc\n && String(row.operation) === operation\n && String(row.description) === description\n ) {\n return { ok: true, balance_mc: await readBalance(), duplicate: true }\n }\n } catch {\n void 0\n }\n throw error\n }\n\n const claim = await db.execute({\n sql: 'SELECT user_id, amount_mc, operation, description, status FROM billing_credits WHERE idempotency_key = ? LIMIT 1',\n args: [idempotencyKey],\n })\n const row = claim.rows[0]\n if (\n !row\n || Number(row.user_id) !== userId\n || Number(row.amount_mc) !== mc\n || String(row.operation) !== operation\n || String(row.description) !== description\n ) {\n throw new Error('idempotent credit key conflicts with another credit')\n }\n\n return {\n ok: String(row.status) === 'applied',\n balance_mc: await readBalance(),\n duplicate: results[1]?.rowsAffected === 0 && String(row.status) === 'applied',\n }\n}\n\nexport async function ledgerExistsForOperation(userId: number, operation: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: 'SELECT 1 FROM ledger WHERE user_id = ? AND operation = ? LIMIT 1',\n args: [userId, operation],\n })\n return res.rows.length > 0\n}\n\nexport async function claimMonthlyFreeRefresh(userId: number, month: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: 'INSERT OR IGNORE INTO free_credit_refreshes (user_id, month) VALUES (?, ?)',\n args: [userId, month],\n })\n return res.rowsAffected > 0\n}\n\nexport async function debitMc(\n userId: number,\n mc: number,\n operation: string,\n description?: string,\n): Promise<{ ok: boolean; balance_mc: number }> {\n const debit = await debitMcIdempotent(\n userId,\n mc,\n operation,\n description ?? '',\n `debit:${randomUUID()}`,\n )\n return { ok: debit.ok, balance_mc: debit.balance_mc }\n}\n\n/**\n * Atomically applies a wallet debit once for a stable billing key.\n *\n * The debit claim, wallet update, credit-lot drain, and ledger row are committed\n * in one libSQL batch. A claim that could not be funded remains retryable, while\n * a completed claim makes retries a read-only success. This is intended for\n * billing lifecycles that must be recoverable after a caller or process crash.\n */\nexport async function debitMcIdempotent(\n userId: number,\n mc: number,\n operation: string,\n description: string,\n idempotencyKey: string,\n): Promise<{ ok: boolean; balance_mc: number; duplicate: boolean }> {\n if (!Number.isSafeInteger(mc) || mc <= 0) throw new Error('idempotent debit amount must be a positive integer')\n if (!idempotencyKey || idempotencyKey.length > 500) throw new Error('idempotent debit key is required and must be bounded')\n\n const db = getDb()\n await ensureMigrated(userId)\n let results\n try {\n results = await db.batch([\n {\n // An unfunded claim may be retried with a corrected estimate under the\n // same logical operation fingerprint. Applied claims remain immutable.\n sql: \"UPDATE billing_debits SET amount_mc = ? WHERE idempotency_key = ? AND user_id = ? AND operation = ? AND description = ? AND status = 'claimed'\",\n args: [mc, idempotencyKey, userId, operation, description],\n },\n {\n sql: \"INSERT OR IGNORE INTO billing_debits (idempotency_key, user_id, amount_mc, operation, description, status) VALUES (?, ?, ?, ?, ?, 'claimed')\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: `UPDATE users\n SET balance_mc = balance_mc - ?\n WHERE id = ?\n AND balance_mc >= ?\n AND (SELECT COALESCE(SUM(remaining_mc), 0)\n FROM credit_lots\n WHERE user_id = ?\n AND remaining_mc > 0\n AND (never_expires = 1 OR expires_at > datetime('now'))) >= ?\n AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed')`,\n args: [mc, userId, mc, userId, mc, idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: \"UPDATE billing_debits SET status = 'applying' WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed' AND changes() = 1\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: `INSERT OR IGNORE INTO billing_debit_allocations\n (debit_idempotency_key, lot_id, user_id, amount_mc, expires_at)\n WITH ordered AS MATERIALIZED (\n SELECT id,\n remaining_mc,\n expires_at,\n COALESCE(SUM(remaining_mc) OVER (\n ORDER BY never_expires ASC, expires_at ASC, granted_at ASC, id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM credit_lots\n WHERE user_id = ? AND remaining_mc > 0 AND (never_expires = 1 OR expires_at > datetime('now'))\n ), allocation AS (\n SELECT id, expires_at, MIN(remaining_mc, MAX(0, ? - prior_mc)) AS take_mc\n FROM ordered\n )\n SELECT ?, id, ?, take_mc, expires_at\n FROM allocation\n WHERE take_mc > 0\n AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,\n args: [userId, mc, idempotencyKey, userId, idempotencyKey],\n },\n {\n sql: `UPDATE credit_lots\n SET remaining_mc = remaining_mc - COALESCE((\n SELECT amount_mc FROM billing_debit_allocations\n WHERE debit_idempotency_key = ? AND lot_id = credit_lots.id\n ), 0)\n WHERE id IN (\n SELECT lot_id FROM billing_debit_allocations WHERE debit_idempotency_key = ?\n )\n AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,\n args: [idempotencyKey, idempotencyKey, idempotencyKey],\n },\n {\n sql: \"INSERT INTO ledger (user_id, amount_mc, operation, description) SELECT user_id, -amount_mc, operation, description FROM billing_debits WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n {\n sql: \"UPDATE billing_debits SET status = 'applied', applied_at = datetime('now') WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n ], 'write')\n } catch (error) {\n // libSQL can report a transport failure after the transaction committed.\n // Read the durable receipt before surfacing the error so callers do not\n // retry a completed debit under a different key.\n try {\n const recovered = await db.execute({\n sql: \"SELECT user_id, amount_mc, operation, description, status FROM billing_debits WHERE idempotency_key = ? AND status = 'applied' LIMIT 1\",\n args: [idempotencyKey],\n })\n const row = recovered.rows[0]\n if (\n row\n && Number(row.user_id) === userId\n && Number(row.amount_mc) === mc\n && String(row.operation) === operation\n && String(row.description) === description\n ) {\n const balance = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return { ok: true, balance_mc: Number(balance.rows[0]?.balance_mc ?? 0), duplicate: true }\n }\n } catch {\n // Preserve the original batch error when the recovery read also fails.\n }\n throw error\n }\n\n const claim = await db.execute({\n sql: 'SELECT user_id, amount_mc, operation, description, status FROM billing_debits WHERE idempotency_key = ? LIMIT 1',\n args: [idempotencyKey],\n })\n const row = claim.rows[0]\n if (\n !row\n || Number(row.user_id) !== userId\n || Number(row.amount_mc) !== mc\n || String(row.operation) !== operation\n || String(row.description) !== description\n ) {\n throw new Error('idempotent debit key conflicts with another debit')\n }\n const balance = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return {\n ok: String(row.status) === 'applied',\n balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),\n duplicate: results[2]?.rowsAffected === 0 && String(row.status) === 'applied',\n }\n}\n\nexport interface IdempotentDebitSettlement {\n balance_mc: number\n final_amount_mc: number\n /** Credits actually restored to the spendable wallet. */\n refund_mc: number\n /** Unused hold that had already expired and therefore was not restored. */\n expired_refund_mc?: number\n duplicate: boolean\n}\n\n/**\n * Atomically settles an idempotent debit hold to its final charge.\n *\n * The first settlement for a hold wins and is persisted as a billing-event\n * receipt. Retrying the same settlement, including after an ambiguous\n * post-commit transport error, reads that receipt and cannot apply a second\n * refund. A later retry that observes a different runtime cost receives the\n * first durable final amount so its public billing result remains consistent\n * with the wallet.\n */\nexport async function settleDebitMcIdempotent(\n userId: number,\n debitIdempotencyKey: string,\n finalAmountMc: number,\n refundOperation: string,\n description: string,\n sourceSurface: string,\n): Promise<IdempotentDebitSettlement> {\n if (!Number.isSafeInteger(finalAmountMc) || finalAmountMc < 0) {\n throw new Error('idempotent debit settlement amount must be a non-negative integer')\n }\n if (!debitIdempotencyKey || debitIdempotencyKey.length > 500) {\n throw new Error('idempotent debit settlement key is required and must be bounded')\n }\n if (!refundOperation || !sourceSurface) {\n throw new Error('idempotent debit settlement operation and source are required')\n }\n\n const db = getDb()\n await ensureMigrated(userId)\n const settlementKey = `debit-settlement:${createHash('sha256').update(debitIdempotencyKey).digest('hex')}`\n const billingClass = 'wallet_hold_settlement'\n const eventId = randomUUID()\n const metadata = JSON.stringify({ debitIdempotencyKey, refundOperation, description })\n\n const readReceipt = async (): Promise<IdempotentDebitSettlement | null> => {\n const receipt = await db.execute({\n sql: `SELECT e.user_id,\n e.status,\n e.amount_mc AS final_amount_mc,\n e.billing_class,\n e.source_surface,\n e.settled_at,\n d.amount_mc AS held_amount_mc\n FROM billing_events e\n JOIN billing_debits d ON d.idempotency_key = ?\n WHERE e.idempotency_key = ?\n AND d.user_id = e.user_id\n AND d.status = 'applied'\n LIMIT 1`,\n args: [debitIdempotencyKey, settlementKey],\n })\n const row = receipt.rows[0]\n if (!row) return null\n if (\n Number(row.user_id) !== userId\n || String(row.billing_class) !== billingClass\n || String(row.source_surface) !== sourceSurface\n ) {\n throw new Error('idempotent debit settlement key conflicts with another settlement')\n }\n if (String(row.status) !== 'settled') return null\n const heldAmountMc = Number(row.held_amount_mc)\n const durableFinalAmountMc = Number(row.final_amount_mc)\n if (\n !Number.isSafeInteger(heldAmountMc)\n || !Number.isSafeInteger(durableFinalAmountMc)\n || durableFinalAmountMc < 0\n || durableFinalAmountMc > heldAmountMc\n ) {\n throw new Error('idempotent debit settlement receipt is invalid')\n }\n const nominalRefundMc = heldAmountMc - durableFinalAmountMc\n const allocationRefund = await db.execute({\n sql: `WITH ordered AS (\n SELECT a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n )\n SELECT (SELECT COUNT(*) FROM ordered) AS allocation_count,\n COALESCE(SUM(CASE WHEN expires_at > ? THEN refund_mc ELSE 0 END), 0) AS credited_refund_mc\n FROM refunds`,\n args: [debitIdempotencyKey, durableFinalAmountMc, String(row.settled_at)],\n })\n const allocationCount = Number(allocationRefund.rows[0]?.allocation_count ?? 0)\n const creditedRefundMc = allocationCount > 0\n ? Number(allocationRefund.rows[0]?.credited_refund_mc ?? 0)\n : 0\n const balance = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return {\n balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),\n final_amount_mc: durableFinalAmountMc,\n refund_mc: creditedRefundMc,\n expired_refund_mc: nominalRefundMc - creditedRefundMc,\n duplicate: true,\n }\n }\n\n let results\n try {\n results = await db.batch([\n {\n sql: `INSERT OR IGNORE INTO billing_events\n (id, user_id, idempotency_key, billing_class, source_surface, status, amount_mc, metadata)\n VALUES (?, ?, ?, ?, ?, 'settling', ?, ?)`,\n args: [eventId, userId, settlementKey, billingClass, sourceSurface, finalAmountMc, metadata],\n },\n {\n sql: `WITH ordered AS (\n SELECT a.lot_id,\n a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT lot_id,\n expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n )\n UPDATE users\n SET balance_mc = balance_mc + CASE\n WHEN EXISTS (SELECT 1 FROM ordered) THEN COALESCE(\n (SELECT SUM(refund_mc) FROM refunds WHERE expires_at > datetime('now')),\n 0\n )\n -- Holds created before allocation provenance cannot be safely\n -- tied back to a source lot. Treat their unused portion as\n -- expired instead of minting spendable credits with a guessed\n -- expiry.\n ELSE 0\n END\n WHERE id = ?\n AND EXISTS (\n SELECT 1\n FROM billing_events e\n JOIN billing_debits d ON d.idempotency_key = ?\n WHERE e.idempotency_key = ?\n AND e.user_id = ?\n AND e.billing_class = ?\n AND e.source_surface = ?\n AND e.status = 'settling'\n AND d.user_id = e.user_id\n AND d.status = 'applied'\n AND e.amount_mc BETWEEN 0 AND d.amount_mc\n )`,\n args: [\n debitIdempotencyKey,\n finalAmountMc,\n userId,\n debitIdempotencyKey,\n settlementKey,\n userId,\n billingClass,\n sourceSurface,\n ],\n },\n {\n sql: `UPDATE billing_events\n SET status = 'applying'\n WHERE idempotency_key = ?\n AND user_id = ?\n AND billing_class = ?\n AND source_surface = ?\n AND status = 'settling'\n AND changes() = 1`,\n args: [settlementKey, userId, billingClass, sourceSurface],\n },\n {\n sql: `WITH ordered AS (\n SELECT a.lot_id,\n a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT lot_id,\n expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n )\n UPDATE credit_lots\n SET remaining_mc = remaining_mc + COALESCE((SELECT refund_mc FROM refunds WHERE refunds.lot_id = credit_lots.id), 0)\n WHERE id IN (\n SELECT lot_id FROM refunds\n WHERE refund_mc > 0 AND expires_at > datetime('now')\n )\n AND EXISTS (SELECT 1 FROM billing_events WHERE idempotency_key = ? AND status = 'applying')`,\n args: [debitIdempotencyKey, finalAmountMc, settlementKey],\n },\n {\n sql: `WITH ordered AS (\n SELECT a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n ), credited AS (\n SELECT e.user_id,\n CASE\n WHEN EXISTS (SELECT 1 FROM ordered) THEN COALESCE(\n (SELECT SUM(refund_mc) FROM refunds WHERE expires_at > datetime('now')),\n 0\n )\n ELSE 0\n END AS refund_mc\n FROM billing_events e\n JOIN billing_debits d ON d.idempotency_key = ?\n WHERE e.idempotency_key = ?\n AND e.status = 'applying'\n )\n INSERT INTO ledger (user_id, amount_mc, operation, description)\n SELECT user_id,\n refund_mc,\n ?,\n ?\n FROM credited\n WHERE refund_mc > 0`,\n args: [\n debitIdempotencyKey,\n finalAmountMc,\n debitIdempotencyKey,\n settlementKey,\n refundOperation,\n description,\n ],\n },\n {\n sql: `UPDATE billing_events\n SET status = 'settled', settled_at = datetime('now')\n WHERE idempotency_key = ?\n AND status = 'applying'`,\n args: [settlementKey],\n },\n ], 'write')\n } catch (error) {\n try {\n const recovered = await readReceipt()\n if (recovered) return recovered\n } catch {\n // Preserve the original batch error when the recovery read also fails.\n }\n throw error\n }\n\n const receipt = await readReceipt()\n if (!receipt) throw new Error('idempotent debit settlement did not reach a durable settled state')\n return { ...receipt, duplicate: results[0]?.rowsAffected === 0 }\n}\n\nexport async function getLedger(userId: number, limit = 50): Promise<LedgerRow[]> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM ledger WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',\n args: [userId, limit],\n })\n return res.rows.map(r => ({\n id: Number(r.id),\n user_id: Number(r.user_id),\n amount_mc: Number(r.amount_mc),\n operation: String(r.operation),\n description: r.description != null ? String(r.description) : null,\n stripe_pi: r.stripe_pi != null ? String(r.stripe_pi) : null,\n created_at: String(r.created_at),\n }))\n}\n\nexport interface LedgerOperationClassification {\n refund: string[]\n grant: string[]\n purchase: string[]\n}\n\nexport interface BillingLedgerSummary {\n spent_mc: number\n refunded_mc: number\n granted_mc: number\n purchased_mc: number\n net_mc: number\n by_operation: Array<{ operation: string; mc: number; count: number }>\n}\n\nexport async function getBillingLedgerSummary(\n userId: number,\n since: string,\n classification: LedgerOperationClassification,\n): Promise<BillingLedgerSummary> {\n const db = getDb()\n const refundPlaceholders = classification.refund.map(() => '?').join(', ')\n const grantPlaceholders = classification.grant.map(() => '?').join(', ')\n const purchasePlaceholders = classification.purchase.map(() => '?').join(', ')\n const totalsRes = await db.execute({\n sql: `SELECT\n COALESCE(SUM(CASE WHEN amount_mc < 0 AND operation NOT IN (${refundPlaceholders}) THEN -amount_mc ELSE 0 END), 0) AS spent_mc,\n COALESCE(SUM(CASE WHEN amount_mc > 0 AND operation IN (${refundPlaceholders}) THEN amount_mc ELSE 0 END), 0) AS refunded_mc,\n COALESCE(SUM(CASE WHEN amount_mc > 0 AND operation IN (${grantPlaceholders}) THEN amount_mc ELSE 0 END), 0) AS granted_mc,\n COALESCE(SUM(CASE WHEN amount_mc > 0 AND operation IN (${purchasePlaceholders}) THEN amount_mc ELSE 0 END), 0) AS purchased_mc,\n COALESCE(SUM(amount_mc), 0) AS net_mc\n FROM ledger WHERE user_id = ? AND created_at >= ?`,\n args: [\n ...classification.refund, ...classification.refund,\n ...classification.grant, ...classification.purchase,\n userId, since,\n ],\n })\n const byOpRes = await db.execute({\n sql: `SELECT operation, -SUM(amount_mc) AS mc, COUNT(*) AS count\n FROM ledger\n WHERE user_id = ? AND created_at >= ? AND amount_mc < 0 AND operation NOT IN (${refundPlaceholders})\n GROUP BY operation\n ORDER BY mc DESC, operation ASC`,\n args: [userId, since, ...classification.refund],\n })\n const totals = totalsRes.rows[0]\n return {\n spent_mc: Number(totals?.spent_mc ?? 0),\n refunded_mc: Number(totals?.refunded_mc ?? 0),\n granted_mc: Number(totals?.granted_mc ?? 0),\n purchased_mc: Number(totals?.purchased_mc ?? 0),\n net_mc: Number(totals?.net_mc ?? 0),\n by_operation: byOpRes.rows.map(r => ({\n operation: String(r.operation),\n mc: Number(r.mc),\n count: Number(r.count),\n })),\n }\n}\n\nexport async function ledgerExistsForStripePI(stripePI: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: 'SELECT 1 FROM ledger WHERE stripe_pi = ? LIMIT 1',\n args: [stripePI],\n })\n return res.rows.length > 0\n}\n\nexport async function reconcileBalanceMc(userId: number | bigint, knownBalanceMc?: number): Promise<number> {\n const db = getDb()\n // The caller's value is only a fast-path hint. Every write compares against\n // the cached balance observed in the same snapshot as the lot projection.\n void knownBalanceMc\n for (let attempt = 0; attempt < 8; attempt += 1) {\n const snapshot = await db.execute({\n sql: `SELECT\n u.balance_mc AS cached_balance_mc,\n COUNT(l.id) AS lot_count,\n COALESCE(SUM(CASE\n WHEN l.remaining_mc > 0\n AND (l.never_expires = 1 OR l.expires_at > datetime('now'))\n THEN l.remaining_mc ELSE 0 END), 0) AS projected_balance_mc\n FROM users u\n LEFT JOIN credit_lots l ON l.user_id = u.id\n WHERE u.id = ?\n GROUP BY u.id, u.balance_mc`,\n args: [userId],\n })\n const row = snapshot.rows[0]\n if (!row) return 0\n\n if (Number(row.lot_count ?? 0) === 0) {\n await ensureMigrated(Number(userId))\n const nowHasLots = await db.execute({\n sql: 'SELECT 1 FROM credit_lots WHERE user_id = ? LIMIT 1',\n args: [userId],\n })\n if (nowHasLots.rows.length > 0) continue\n }\n\n const cachedBalanceMc = Number(row.cached_balance_mc ?? 0)\n const projectedBalanceMc = Number(row.projected_balance_mc ?? 0)\n if (cachedBalanceMc === projectedBalanceMc) return projectedBalanceMc\n\n const updated = await db.execute({\n sql: 'UPDATE users SET balance_mc = ? WHERE id = ? AND balance_mc = ?',\n args: [projectedBalanceMc, userId, cachedBalanceMc],\n })\n if (updated.rowsAffected === 1) return projectedBalanceMc\n }\n\n // Under sustained contention, return the last committed wallet balance\n // instead of risking a stale projection write.\n const current = await db.execute({\n sql: 'SELECT balance_mc FROM users WHERE id = ? LIMIT 1',\n args: [userId],\n })\n return Number(current.rows[0]?.balance_mc ?? 0)\n}\n\nexport interface LedgerRow {\n id: number\n user_id: number\n amount_mc: number\n operation: string\n description: string | null\n stripe_pi: string | null\n created_at: string\n}\n\nexport const SiteAuditJobRowSchema = z.object({\n id: z.string(),\n user_id: z.number(),\n status: z.string().default('pending'),\n client_domain: z.string(),\n session_path: z.string(),\n request: z.string(),\n result: z.string().nullable(),\n error: z.string().nullable(),\n created_at: z.string(),\n updated_at: z.string(),\n});\nexport type SiteAuditJobRow = z.infer<typeof SiteAuditJobRowSchema>;\n\nexport const SiteAuditPhaseLogRowSchema = z.object({\n id: z.number(),\n job_id: z.string(),\n phase: z.string(),\n completed_at: z.string(),\n output_summary: z.string(),\n});\nexport type SiteAuditPhaseLogRow = z.infer<typeof SiteAuditPhaseLogRowSchema>;\n\nexport interface InboxMessageSummary {\n id: string\n subject: string\n sent_at: string\n opened_at: string | null\n}\n\nexport interface InboxMessage extends InboxMessageSummary {\n body_md: string\n source: string\n}\n\nexport async function listInboxMessages(userId: number | bigint): Promise<InboxMessageSummary[]> {\n const res = await getDb().execute({\n sql: 'SELECT id, subject, sent_at, opened_at FROM inbox_messages WHERE user_id = ? AND deleted_at IS NULL ORDER BY sent_at DESC, id DESC',\n args: [userId],\n })\n return res.rows.map(r => ({\n id: String(r.id),\n subject: String(r.subject),\n sent_at: String(r.sent_at),\n opened_at: r.opened_at != null ? String(r.opened_at) : null,\n }))\n}\n\nexport async function getInboxMessage(userId: number | bigint, id: string): Promise<InboxMessage | undefined> {\n const res = await getDb().execute({\n sql: 'SELECT id, subject, body_md, source, sent_at, opened_at FROM inbox_messages WHERE id = ? AND user_id = ? AND deleted_at IS NULL',\n args: [id, userId],\n })\n const row = res.rows[0]\n if (!row) return undefined\n return {\n id: String(row.id),\n subject: String(row.subject),\n body_md: String(row.body_md),\n source: String(row.source),\n sent_at: String(row.sent_at),\n opened_at: row.opened_at != null ? String(row.opened_at) : null,\n }\n}\n\nexport async function markInboxMessageOpened(userId: number | bigint, id: string): Promise<string | null> {\n const db = getDb()\n await db.execute({\n sql: \"UPDATE inbox_messages SET opened_at = datetime('now') WHERE id = ? AND user_id = ? AND deleted_at IS NULL AND opened_at IS NULL\",\n args: [id, userId],\n })\n const res = await db.execute({\n sql: 'SELECT opened_at FROM inbox_messages WHERE id = ? AND user_id = ? AND deleted_at IS NULL',\n args: [id, userId],\n })\n const row = res.rows[0]\n if (!row) return null\n return row.opened_at != null ? String(row.opened_at) : null\n}\n\nexport async function softDeleteInboxMessage(userId: number | bigint, id: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: \"UPDATE inbox_messages SET deleted_at = datetime('now') WHERE id = ? AND user_id = ? AND deleted_at IS NULL\",\n args: [id, userId],\n })\n return res.rowsAffected > 0\n}\n\nexport async function insertInboxMessage(userId: number | bigint, subject: string, bodyMd: string, source = 'operator'): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: 'INSERT INTO inbox_messages (id, user_id, subject, body_md, source) VALUES (?, ?, ?, ?, ?)',\n args: [id, userId, subject, bodyMd, source],\n })\n return id\n}\n\nexport async function listActiveUserIds(): Promise<number[]> {\n const res = await getDb().execute('SELECT id FROM users WHERE active = 1')\n return res.rows.map(r => Number(r.id))\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\n\nexport interface CostContext {\n op: string\n probeRunId?: string | null\n userId?: number | null\n forceHeadless?: boolean\n forceHeadful?: boolean\n subOp?: string | null\n headlessSentOut?: { value: boolean | null }\n}\n\nconst storage = new AsyncLocalStorage<CostContext>()\n\nexport function runWithCostContext<T>(ctx: CostContext, fn: () => T): T {\n return storage.run(ctx, fn)\n}\n\nexport function currentCostContext(): CostContext | undefined {\n return storage.getStore()\n}\n","export const KERNEL_HEADLESS_USD_PER_SEC = 0.0000166667\nexport const KERNEL_HEADFUL_USD_PER_SEC = 0.0001333336\n\nexport const HEADLESS_OPS = new Set<string>(['serp', 'fb_search', 'fb_ad', 'instagram'])\n\nexport const FAL_WIZPER_USD_PER_COMPUTE_SEC = 0.00111\nexport const FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR = 4 // calibrated from 2 real Fal invoices (4.36x, 6.18x wall-clock vs actual)\nexport const DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS = 0.0004\n\nfunction envRate(name: string, fallback: number): number {\n const raw = process.env[name]?.trim()\n if (!raw) return fallback\n const parsed = Number(raw)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback\n}\n\nexport const NANGO_USD_PER_CONNECTION_MONTH = envRate('NANGO_USD_PER_CONNECTION_MONTH', 1)\nexport const NANGO_USD_PER_FUNCTION_RUN = envRate('NANGO_USD_PER_FUNCTION_RUN', 0.0001)\nexport const NANGO_USD_PER_PROXY_REQUEST = envRate('NANGO_USD_PER_PROXY_REQUEST', 0.0001)\nexport const NANGO_USD_PER_COMPUTE_SEC = envRate('NANGO_USD_PER_COMPUTE_SEC', 0.0002)\n\nexport const CONNECTED_TARGET_MARGIN_MULTIPLE = 2.25\n\nexport function kernelCostUsd(ms: number, headful: boolean): number {\n const sec = Math.max(0, ms) / 1000\n return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC)\n}\n\nexport function vendorCostUsd(vendor: string, units: number): number {\n if (vendor === 'fal_wizper') {\n const estimatedComputeSec = Math.max(0, units) / FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR\n return estimatedComputeSec * FAL_WIZPER_USD_PER_COMPUTE_SEC\n }\n if (vendor === 'deepinfra_qwen') return (Math.max(0, units) / 1000) * DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS\n if (vendor === 'openrouter') return Math.max(0, units) // units IS the real USD cost OpenRouter reported, not a rate-multiplied estimate\n if (vendor === 'mcp_memory_video') return Math.max(0, units) // units IS the real costUsd mcp-memory's video pipeline reported (OpenRouter + transcription)\n if (vendor === 'mcp_memory_ai') return Math.max(0, units) // units IS the real costUsd mcp-memory reported via /ai-debit\n if (vendor === 'nango_connection') return Math.max(0, units) * NANGO_USD_PER_CONNECTION_MONTH\n if (vendor === 'nango_function_run') return Math.max(0, units) * NANGO_USD_PER_FUNCTION_RUN\n if (vendor === 'nango_proxy_request') return Math.max(0, units) * NANGO_USD_PER_PROXY_REQUEST\n if (vendor === 'nango_compute') return Math.max(0, units) * NANGO_USD_PER_COMPUTE_SEC\n return 0\n}\n","import type { CanonicalLocationName } from './types.js'\n\nexport const LOCATIONS: Record<string, CanonicalLocationName> = {\n 'austin': 'Austin,Texas,United States',\n 'new york': 'New York,New York,United States',\n 'new york city': 'New York,New York,United States',\n 'nyc': 'New York,New York,United States',\n 'los angeles': 'Los Angeles,California,United States',\n 'la': 'Los Angeles,California,United States',\n 'chicago': 'Chicago,Illinois,United States',\n 'houston': 'Houston,Texas,United States',\n 'phoenix': 'Phoenix,Arizona,United States',\n 'philadelphia': 'Philadelphia,Pennsylvania,United States',\n 'philly': 'Philadelphia,Pennsylvania,United States',\n 'san antonio': 'San Antonio,Texas,United States',\n 'dallas': 'Dallas,Texas,United States',\n 'miami': 'Miami,Florida,United States',\n 'seattle': 'Seattle,Washington,United States',\n 'denver': 'Denver,Colorado,United States',\n 'loveland': 'Loveland,Colorado,United States',\n 'loveland co': 'Loveland,Colorado,United States',\n 'fort collins': 'Fort Collins,Colorado,United States',\n 'boulder': 'Boulder,Colorado,United States',\n 'colorado springs': 'Colorado Springs,Colorado,United States',\n 'boston': 'Boston,Massachusetts,United States',\n 'atlanta': 'Atlanta,Georgia,United States',\n 'san francisco': 'San Francisco,California,United States',\n 'sf': 'San Francisco,California,United States',\n 'portland': 'Portland,Oregon,United States',\n 'las vegas': 'Las Vegas,Nevada,United States',\n 'minneapolis': 'Minneapolis,Minnesota,United States',\n 'detroit': 'Detroit,Michigan,United States',\n 'nashville': 'Nashville,Tennessee,United States',\n 'charlotte': 'Charlotte,North Carolina,United States',\n 'orlando': 'Orlando,Florida,United States',\n 'san diego': 'San Diego,California,United States',\n 'baltimore': 'Baltimore,Maryland,United States',\n 'sacramento': 'Sacramento,California,United States',\n 'columbus': 'Columbus,Ohio,United States',\n 'indianapolis': 'Indianapolis,Indiana,United States',\n 'san jose': 'San Jose,California,United States',\n 'fort worth': 'Fort Worth,Texas,United States',\n 'jacksonville': 'Jacksonville,Florida,United States',\n 'memphis': 'Memphis,Tennessee,United States',\n 'louisville': 'Louisville,Kentucky,United States',\n 'raleigh': 'Raleigh,North Carolina,United States',\n 'richmond': 'Richmond,Virginia,United States',\n 'salt lake city': 'Salt Lake City,Utah,United States',\n 'toronto': 'Toronto,Ontario,Canada',\n 'vancouver': 'Vancouver,British Columbia,Canada',\n 'montreal': 'Montreal,Quebec,Canada',\n 'calgary': 'Calgary,Alberta,Canada',\n 'ottawa': 'Ottawa,Ontario,Canada',\n 'london': 'London,England,United Kingdom',\n 'manchester': 'Manchester,England,United Kingdom',\n 'birmingham': 'Birmingham,England,United Kingdom',\n 'edinburgh': 'Edinburgh,Scotland,United Kingdom',\n 'glasgow': 'Glasgow,Scotland,United Kingdom',\n 'leeds': 'Leeds,England,United Kingdom',\n 'sydney': 'Sydney,New South Wales,Australia',\n 'melbourne': 'Melbourne,Victoria,Australia',\n 'brisbane': 'Brisbane,Queensland,Australia',\n 'perth': 'Perth,Western Australia,Australia',\n 'adelaide': 'Adelaide,South Australia,Australia',\n 'dublin': 'Dublin,Leinster,Ireland',\n}\n","import type { CanonicalLocationName, UuleString } from './types.js'\nimport { LOCATIONS } from './locations.js'\n\nfunction encodeVarint(value: number): number[] {\n const bytes: number[] = []\n let remaining = value\n do {\n let byte = remaining & 0x7f\n remaining >>>= 7\n if (remaining > 0) byte |= 0x80\n bytes.push(byte)\n } while (remaining > 0)\n return bytes\n}\n\nexport function encodeUule(name: CanonicalLocationName): UuleString {\n const locationBytes = Buffer.from(name, 'utf8')\n const payload = Buffer.concat([\n Buffer.from([0x08, 0x02, 0x10, 0x20, 0x22]),\n Buffer.from(encodeVarint(locationBytes.length)),\n locationBytes,\n ])\n return `w+${payload.toString('base64')}`\n}\n\nexport function normalizeLocation(input: string): CanonicalLocationName {\n const raw = input.toLowerCase().trim()\n if (LOCATIONS[raw]) return LOCATIONS[raw] as CanonicalLocationName\n const beforeComma = raw.split(',')[0].trim()\n if (beforeComma !== raw && LOCATIONS[beforeComma]) return LOCATIONS[beforeComma] as CanonicalLocationName\n const withoutState = raw.replace(/\\s+[a-z]{2}$/, '').trim()\n if (withoutState !== raw && LOCATIONS[withoutState]) return LOCATIONS[withoutState] as CanonicalLocationName\n return input as CanonicalLocationName\n}\n","import type { CanonicalLocationName } from './types.js'\n\nfunction normalizedTerms(value: string): string[] {\n return value\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter(Boolean)\n}\n\nfunction includesTermSequence(query: string, phrase: string): boolean {\n const queryTerms = normalizedTerms(query)\n const phraseTerms = normalizedTerms(phrase)\n if (phraseTerms.length === 0 || phraseTerms.length > queryTerms.length) return false\n\n return queryTerms.some((_, start) => phraseTerms.every((term, index) => queryTerms[start + index] === term))\n}\n\nfunction cityFromCanonicalLocation(location: CanonicalLocationName, fallback: string): string {\n return location.split(',', 1)[0]?.trim() || fallback.split(',', 1)[0]?.trim() || fallback.trim()\n}\n\n/**\n * Makes a location request visible to Google without making callers duplicate\n * a city that is already in their query. UULE carries the same location signal\n * separately; this function only determines the search text.\n */\nexport function buildLocalizedSerpQuery(\n query: string,\n location: string | undefined,\n canonicalLocation: CanonicalLocationName | null,\n): string {\n const normalizedQuery = query.trim()\n const requestedLocation = location?.trim()\n if (!requestedLocation || !canonicalLocation) return normalizedQuery\n\n const city = cityFromCanonicalLocation(canonicalLocation, requestedLocation)\n return includesTermSequence(normalizedQuery, city)\n ? normalizedQuery\n : `${normalizedQuery} ${requestedLocation}`\n}\n","import type { CanonicalLocationName, LocalPackBusiness, OrganicResult, SerpLocationCandidate, SerpLocationEvidence } from './types.js'\n\nconst STATE_TO_CODE: Record<string, string> = {\n alabama: 'AL', alaska: 'AK', arizona: 'AZ', arkansas: 'AR', california: 'CA',\n colorado: 'CO', connecticut: 'CT', delaware: 'DE', florida: 'FL', georgia: 'GA',\n hawaii: 'HI', idaho: 'ID', illinois: 'IL', indiana: 'IN', iowa: 'IA',\n kansas: 'KS', kentucky: 'KY', louisiana: 'LA', maine: 'ME', maryland: 'MD',\n massachusetts: 'MA', michigan: 'MI', minnesota: 'MN', mississippi: 'MS', missouri: 'MO',\n montana: 'MT', nebraska: 'NE', nevada: 'NV', 'new hampshire': 'NH', 'new jersey': 'NJ',\n 'new mexico': 'NM', 'new york': 'NY', 'north carolina': 'NC', 'north dakota': 'ND', ohio: 'OH',\n oklahoma: 'OK', oregon: 'OR', pennsylvania: 'PA', 'rhode island': 'RI', 'south carolina': 'SC',\n 'south dakota': 'SD', tennessee: 'TN', texas: 'TX', utah: 'UT', vermont: 'VT',\n virginia: 'VA', washington: 'WA', 'west virginia': 'WV', wisconsin: 'WI', wyoming: 'WY',\n 'district of columbia': 'DC',\n}\n\nconst STATE_PATTERN = [\n ...Object.keys(STATE_TO_CODE).map(s => s.replace(/\\s+/g, '\\\\s+')),\n ...Object.values(STATE_TO_CODE),\n].join('|')\n\nconst CITY_STATE_RE = new RegExp(`\\\\b([A-Z][A-Za-z]+(?:[\\\\s.-][A-Z][A-Za-z]+){0,4}),?\\\\s+(${STATE_PATTERN})\\\\b`, 'gi')\n\nfunction normalizeRegionCode(input: string | null | undefined): string | null {\n if (!input) return null\n const trimmed = input.trim()\n if (/^[A-Z]{2}$/i.test(trimmed)) return trimmed.toUpperCase()\n return STATE_TO_CODE[trimmed.toLowerCase()] ?? null\n}\n\nfunction normalizeCity(input: string): string {\n const cleaned = input\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^.*\\b(?:in|near|around|serving)\\s+/i, '')\n return cleaned.toLowerCase().replace(/\\b[a-z]/g, char => char.toUpperCase())\n}\n\nfunction parseExpected(canonicalLocation: CanonicalLocationName | null): SerpLocationEvidence['expected'] {\n if (!canonicalLocation) return null\n const [city = '', region = ''] = canonicalLocation.split(',').map(part => part.trim())\n return {\n city: normalizeCity(city),\n regionCode: normalizeRegionCode(region),\n canonicalLocation,\n }\n}\n\nfunction addCandidate(\n candidates: Map<string, SerpLocationCandidate>,\n city: string,\n region: string,\n example: string,\n): void {\n const normalizedCity = normalizeCity(city)\n const regionCode = normalizeRegionCode(region)\n if (!normalizedCity || !regionCode) return\n const key = `${normalizedCity.toLowerCase()}|${regionCode}`\n const existing = candidates.get(key)\n if (existing) {\n existing.count++\n if (existing.examples.length < 3 && !existing.examples.includes(example)) existing.examples.push(example)\n return\n }\n candidates.set(key, { city: normalizedCity, regionCode, count: 1, examples: [example] })\n}\n\nfunction decodeSerpText(text: string): string {\n try {\n return decodeURIComponent(text)\n } catch {}\n\n try {\n return decodeURIComponent(text.replace(/%(?![0-9a-fA-F]{2})/g, '%25'))\n } catch {\n return text\n }\n}\n\nfunction scanText(candidates: Map<string, SerpLocationCandidate>, text: string): void {\n const normalized = decodeSerpText(text).replace(/[+/|_-]+/g, ' ')\n for (const match of normalized.matchAll(CITY_STATE_RE)) {\n addCandidate(candidates, match[1] ?? '', match[2] ?? '', normalized.slice(0, 180))\n }\n}\n\nexport function inferSerpLocationEvidence(\n canonicalLocation: CanonicalLocationName | null,\n organicResults: OrganicResult[],\n localPack: LocalPackBusiness[],\n): SerpLocationEvidence {\n const expected = parseExpected(canonicalLocation)\n const candidates = new Map<string, SerpLocationCandidate>()\n\n for (const result of organicResults) {\n scanText(candidates, [result.title, result.snippet ?? '', result.cite ?? '', result.url].join(' '))\n }\n for (const business of localPack) {\n scanText(candidates, [business.name, ...business.metadata, business.websiteUrl ?? '', business.directionsUrl ?? ''].join(' '))\n }\n\n const rankedCandidates = Array.from(candidates.values())\n .sort((a, b) => b.count - a.count || a.city.localeCompare(b.city))\n .slice(0, 8)\n\n if (!expected) {\n return { status: 'not_requested', expected: null, candidates: rankedCandidates }\n }\n if (rankedCandidates.length === 0) {\n return { status: 'unknown', expected, candidates: [] }\n }\n\n const matched = rankedCandidates.some(candidate =>\n candidate.city.toLowerCase() === expected.city.toLowerCase()\n && (expected.regionCode == null || candidate.regionCode === expected.regionCode)\n )\n\n return {\n status: matched ? 'matched' : 'mismatch',\n expected,\n candidates: rankedCandidates,\n }\n}\n","const MAX_ANSWER_LENGTH = 1200\n\nconst BOILERPLATE_PATTERNS: RegExp[] = [\n /An AI Overview is not available for this search/gi,\n /Can't generate an AI overview right now\\.?\\s*Try again later\\.?/gi,\n /\\bAI Overview\\b/gi,\n /\\bView all\\b/gi,\n]\n\nconst CUT_MARKERS: RegExp[] = [\n /\\bRelated Links\\b/i,\n /\\bAsk anything in\\s*AI Mode\\b/i,\n /\\bAI can make mistakes\\b/i,\n /\\bThis is for informational purposes only\\b/i,\n /\\bShow more\\b/i,\n /\\b\\d+\\s+sites\\b/i,\n /\\b\\d{1,2}\\s*[msh]\\s*[A-Z][A-Za-z]/,\n /\\b(?:YouTube|Reddit|Facebook|Instagram|TikTok)·/,\n]\n\nfunction normalizeWhitespace(text: string): string {\n return text\n .replace(/\\u00a0/g, ' ')\n .replace(/([.!?])([A-Z])/g, '$1 $2')\n .replace(/([:;])([A-Z])/g, '$1 $2')\n .replace(/([a-z])([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([A-Z][a-z])/g, '$1 $2')\n .replace(/([a-z])(\\d)/g, '$1 $2')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction cutAtFirstMarker(text: string): string {\n let cutAt = -1\n for (const marker of CUT_MARKERS) {\n const match = marker.exec(text)\n marker.lastIndex = 0\n if (match && (cutAt === -1 || match.index < cutAt)) cutAt = match.index\n }\n return cutAt === -1 ? text : text.slice(0, cutAt)\n}\n\nfunction cutAtSourceTitle(text: string, sourceTitle?: string): string {\n const title = sourceTitle?.trim()\n if (!title || title.length < 8) return text\n const idx = text.toLowerCase().indexOf(title.toLowerCase())\n return idx > 40 ? text.slice(0, idx) : text\n}\n\nfunction findAttributionCut(beforeUrl: string): number {\n const dateMatch = beforeUrl.match(/[•·]\\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+\\d{1,2},\\s+\\d{4}/i)\n if (dateMatch?.index && dateMatch.index > 40) return dateMatch.index\n\n const start = Math.max(0, beforeUrl.length - 260)\n const tail = beforeUrl.slice(start)\n const sentenceBreaks = [...tail.matchAll(/[.!?]\\s*(?=[A-Z][A-Za-z0-9\"'$])/g)]\n for (const match of sentenceBreaks) {\n const remainder = tail.slice(match.index + 1).trim()\n const lead = remainder.slice(0, 160)\n const looksLikeTitle = /^(?:Best|Top|What|How|Why|When|Where|Which|Can|Should|Is|Are|Do|Does)\\b/i.test(remainder)\n if (\n remainder.length > 20 &&\n looksLikeTitle &&\n /(?:\\s[-|]\\s|Heating|Cooling|Company|Services|Blog|Guide|Review)/i.test(lead)\n ) {\n return start + match.index + 1\n }\n }\n const last = sentenceBreaks.at(-1)\n if (last?.index !== undefined) return start + last.index + 1\n\n return beforeUrl.length\n}\n\nfunction cutAtUrlAttribution(text: string): string {\n const urlMatch = text.match(/https?:\\/\\/\\S+/i)\n if (!urlMatch?.index) return text\n\n const beforeUrl = text.slice(0, urlMatch.index)\n return beforeUrl.slice(0, findAttributionCut(beforeUrl))\n}\n\nfunction trimToSentenceLimit(text: string): string {\n if (text.length <= MAX_ANSWER_LENGTH) return text\n const slice = text.slice(0, MAX_ANSWER_LENGTH)\n const lastSentence = Math.max(slice.lastIndexOf('.'), slice.lastIndexOf('!'), slice.lastIndexOf('?'))\n return (lastSentence > 240 ? slice.slice(0, lastSentence + 1) : slice).trim()\n}\n\nexport function cleanPAAAnswerText(\n answer: string | null | undefined,\n question?: string,\n sourceTitle?: string,\n): string | undefined {\n if (!answer) return undefined\n\n let text = normalizeWhitespace(answer)\n const normalizedQuestion = question ? normalizeWhitespace(question) : ''\n if (normalizedQuestion && text.toLowerCase().startsWith(normalizedQuestion.toLowerCase())) {\n text = text.slice(normalizedQuestion.length).trim()\n }\n\n if (/^An error has occurred\\.?\\s*Please try again later\\.?/i.test(text)) {\n return undefined\n }\n\n for (const pattern of BOILERPLATE_PATTERNS) {\n text = text.replace(pattern, ' ')\n }\n text = text\n .replace(/\\b[A-Z][A-Za-z&'\\u2019 -]{2,60}\\+\\d+\\b/g, ' ')\n .replace(/\\b(?:[a-z0-9-]+\\.)+[a-z]{2,}\\+\\d+\\b/gi, ' ')\n\n text = normalizeWhitespace(text)\n text = cutAtFirstMarker(text)\n text = cutAtSourceTitle(text, sourceTitle)\n text = cutAtUrlAttribution(text)\n text = normalizeWhitespace(text)\n text = text.replace(/\\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+\\d{1,2},\\s+\\d{4}$/i, '').trim()\n text = trimToSentenceLimit(text)\n\n if (!text) return undefined\n if (/^An error has occurred\\.?\\s*Please try again later\\.?$/i.test(text)) return undefined\n\n return text\n}\n","export interface AISurfaceSelectorConfig {\n aio: {\n root: string\n legacyRoot?: string\n wrapper?: string\n controller: string\n contentSubtree: string\n heading: string\n header: string\n showMoreButton: string\n sourcesPanel: string\n disclaimer: string\n }\n aim: {\n root: string\n wrapper: string\n }\n expandWaitMs?: number\n}\n\nexport async function extractAISurfacesFromDocument(config?: AISurfaceSelectorConfig): Promise<{\n surface: 'web' | 'aim' | 'unknown'\n aiOverview: {\n detected: boolean\n text: string | null\n citations: Array<{ text: string; href: string }>\n expanded: boolean\n fullyExpanded: boolean\n sections: string[]\n }\n aiMode: {\n detected: boolean\n text: string | null\n citations: Array<{ text: string; href: string }>\n }\n}> {\n const selectors = config ?? {\n aio: {\n root: '[data-lhcontainer][data-streaming-container][eid]',\n legacyRoot: '[data-hveid=\"CBMQAA\"]',\n wrapper: '.Fgyi2e',\n controller: '[jscontroller=\"AkrxPe\"]',\n contentSubtree: '[data-subtree=\"mfc\"]',\n heading: '.Fzsovc.cwYVJe.RJPOee',\n header: '.heWuVc',\n showMoreButton: '[aria-label=\"Show more AI Overview\"]',\n sourcesPanel: '.OZ9ddf.WAUd4',\n disclaimer: '.DuQANe.MSJHRb',\n },\n aim: {\n root: '[data-hveid=\"CAUQAA\"]',\n wrapper: '.Fgyi2e',\n },\n expandWaitMs: 1500,\n }\n\n const sn: string = (window as unknown as { google?: { sn?: string } }).google?.sn ?? 'unknown'\n const surface: 'web' | 'aim' | 'unknown' =\n sn === 'aim' ? 'aim' : sn === 'web' ? 'web' : 'unknown'\n\n function textOf(el: Element | null): string {\n if (!el) return ''\n return ((el as HTMLElement).innerText ?? el.textContent ?? '').trim()\n }\n\n function hasSubstantiveContent(text: string | null): text is string {\n if (!text) return false\n const normalized = text.replace(/\\s+/g, ' ').trim()\n return normalized.length >= 16 && normalized.split(' ').filter(Boolean).length >= 2\n }\n\n function hasAIOverviewLabel(el: Element): boolean {\n const heading = el.querySelector(selectors.aio.heading)\n if (textOf(heading) === 'AI Overview') return true\n const header = el.querySelector(selectors.aio.header)\n if (textOf(header).split(/\\n|\\s{2,}/).some(part => part.trim() === 'AI Overview')) return true\n return textOf(el).includes('AI Overview')\n }\n\n function findAIORoot(): Element | null {\n const primaryRoots = Array.from(document.querySelectorAll(selectors.aio.root))\n const labeledPrimary = primaryRoots.find(hasAIOverviewLabel)\n if (labeledPrimary) return labeledPrimary\n if (primaryRoots.length > 0) return primaryRoots[0]\n\n if (selectors.aio.legacyRoot) {\n const legacy = document.querySelector(selectors.aio.legacyRoot)\n if (legacy) return legacy\n }\n\n const headings = document.querySelectorAll(`${selectors.aio.heading}, h1, h2, h3, [role=\"heading\"]`)\n for (const h of headings) {\n if (textOf(h) !== 'AI Overview') continue\n let el: Element | null = h.parentElement\n for (let i = 0; i < 8 && el; i++) {\n if (\n el.matches(selectors.aio.root) ||\n el.querySelector(selectors.aio.controller) ||\n el.querySelector(selectors.aio.contentSubtree)\n ) {\n return el\n }\n el = el.parentElement\n }\n return h.parentElement\n }\n return null\n }\n\n function cleanText(target: Element | null): string | null {\n if (!target) return null\n const clone = target.cloneNode(true) as Element\n clone.querySelectorAll([\n 'script',\n 'style',\n 'noscript',\n 'img',\n 'picture',\n 'video',\n selectors.aio.header,\n selectors.aio.showMoreButton,\n selectors.aio.sourcesPanel,\n selectors.aio.disclaimer,\n '[data-subtree=\"dfa\"]',\n '[data-src-id]',\n '[role=\"dialog\"]',\n '.HWMcu',\n '.bTFeG',\n '.CyMdWb',\n '.MFrAxb',\n '.F0OfWd.hfWAgb',\n '.x2qcTc.fZavHb',\n '.SvjEff',\n '.sR2MY',\n '.lKuDef',\n '.GSPQcc',\n 'a[href]',\n 'button',\n '[role=\"button\"]',\n ].join(',')).forEach(el => el.remove())\n\n const holder = document.createElement('div')\n holder.style.position = 'fixed'\n holder.style.left = '-10000px'\n holder.style.top = '0'\n holder.style.width = `${Math.max(320, Math.round((target as HTMLElement).getBoundingClientRect?.().width || 960))}px`\n holder.style.opacity = '0'\n holder.style.pointerEvents = 'none'\n holder.append(clone)\n document.body.append(holder)\n\n const rendered = ((clone as HTMLElement).innerText || clone.textContent || '')\n holder.remove()\n\n const lines = rendered\n .replace(/\\r/g, '')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n[ \\t]+/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/[ \\t]{2,}/g, ' ')\n .trim()\n .split('\\n')\n .map(line => line.replace(/\\u00a0/g, ' ').trim())\n .filter(Boolean)\n\n const filteredLines: string[] = []\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const next = lines[i + 1] ?? ''\n if (line === 'AI Overview') continue\n if (line === 'Show more') continue\n if (/^AI can make mistakes/i.test(line)) continue\n if (/^Thank you\\b/i.test(line)) continue\n if (/^Your feedback helps Google improve/i.test(line)) continue\n if (/^\\+?\\d+$/.test(line)) continue\n if (/^\\+\\d+$/.test(next) && line.length <= 80) {\n i++\n continue\n }\n filteredLines.push(line)\n }\n\n const raw = filteredLines\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n\n if (!raw || /not available|try again|can't generate/i.test(raw)) return null\n return raw\n }\n\n function normalizeHref(rawHref: string): string | null {\n if (!rawHref || rawHref.startsWith('javascript:')) return null\n let href = rawHref\n try {\n const absolute = new URL(rawHref, window.location.href)\n const q = absolute.searchParams.get('q') ?? absolute.searchParams.get('url')\n if (/(\\.|^)google\\./i.test(absolute.hostname) && q?.startsWith('http')) {\n href = q\n } else {\n href = absolute.href\n }\n } catch {\n return null\n }\n\n if (!/^https?:\\/\\//i.test(href)) return null\n try {\n const url = new URL(href)\n const isGoogleInternal = /(\\.|^)google\\./i.test(url.hostname)\n if (isGoogleInternal) return null\n return url.href\n } catch {\n return null\n }\n }\n\n function extractCitations(root: Element | null): Array<{ text: string; href: string }> {\n if (!root) return []\n const seen = new Set<string>()\n const citations: Array<{ text: string; href: string }> = []\n\n for (const a of Array.from(root.querySelectorAll('a[href]'))) {\n const href = normalizeHref(a.getAttribute('href') ?? '')\n if (!href || seen.has(href)) continue\n seen.add(href)\n\n let fallbackHost = ''\n try {\n fallbackHost = new URL(href).hostname.replace(/^www\\./, '')\n } catch {}\n\n citations.push({\n text: textOf(a) || fallbackHost || href,\n href,\n })\n }\n\n return citations\n }\n\n async function maybeExpand(root: Element): Promise<boolean> {\n const button = root.querySelector(selectors.aio.showMoreButton)\n if (!button || button.getAttribute('aria-expanded') !== 'false') return false\n ;(button as HTMLElement).click()\n const waitMs = selectors.expandWaitMs ?? 1500\n if (waitMs > 0) await new Promise(resolve => setTimeout(resolve, waitMs))\n return true\n }\n\n const aioRoot = findAIORoot()\n let aioText: string | null = null\n let aioCitations: Array<{ text: string; href: string }> = []\n let aioExpanded = false\n let aioFullyExpanded = false\n let aioSections: string[] = []\n\n if (aioRoot) {\n aioExpanded = await maybeExpand(aioRoot)\n const controller = aioRoot.querySelector(selectors.aio.controller)\n const contentSubtree = aioRoot.querySelector(selectors.aio.contentSubtree)\n const showMore = aioRoot.querySelector(selectors.aio.showMoreButton)\n aioFullyExpanded =\n controller?.getAttribute('data-trnct') === 'false' ||\n showMore?.getAttribute('aria-expanded') === 'true' ||\n !showMore\n\n aioText = cleanText(contentSubtree ?? controller ?? aioRoot)\n aioSections = (aioText ?? '')\n .split('\\n')\n .map(line => line.trim())\n .filter(line => /^\\d+\\.\\s+.+/.test(line))\n aioCitations = extractCitations(aioRoot)\n }\n\n const aimRoot = document.querySelector(selectors.aim.root)\n const aimRootDetected = surface === 'aim' && !!aimRoot\n const aimContainer = aimRoot?.closest(selectors.aim.wrapper) ?? aimRoot\n const rawAimText = aimRootDetected ? cleanText(aimContainer) : null\n const aimDetected = aimRootDetected && hasSubstantiveContent(rawAimText)\n const aimText = aimDetected ? rawAimText : null\n const aimCitations = aimDetected ? extractCitations(aimContainer) : []\n\n const aiOverviewDetected = !!aioRoot && hasSubstantiveContent(aioText)\n\n return {\n surface,\n aiOverview: {\n detected: aiOverviewDetected,\n text: aiOverviewDetected ? aioText : null,\n citations: aiOverviewDetected ? aioCitations : [],\n expanded: aiOverviewDetected ? aioExpanded : false,\n fullyExpanded: aiOverviewDetected ? aioFullyExpanded : false,\n sections: aiOverviewDetected ? aioSections : [],\n },\n aiMode: {\n detected: aimDetected && aimText !== null,\n text: aimText,\n citations: aimCitations,\n },\n }\n}\n","import type { Page } from 'playwright'\nimport type { IBrowserDriver } from '../driver/IBrowserDriver.js'\nimport type { IProgressReporter } from '../output/IProgressReporter.js'\nimport { PAASelectors, VideoSelectors, ForumSelectors, ShortVideoSelectors, AIOverviewSelectors, AIModeSelectors, WhatPeopleSayingSelectors, OrganicSelectors, LocalPackSelectors } from '../selectors.js'\nimport { encodeUule, normalizeLocation } from '../uule.js'\nimport { buildLocalizedSerpQuery } from '../serp-location-query.js'\nimport { inferSerpLocationEvidence } from '../serp-location-debug.js'\nimport { RawPAAItemSchema, recencyToTbs } from '../schemas.js'\nimport { cleanPAAAnswerText } from '../lib/paa-answer-cleanup.js'\nimport { CaptchaError, RequestAbortedError } from '../errors.js'\nimport { extractAISurfacesFromDocument } from './ai-surfaces.js'\nimport type {\n HarvestOptions, HarvestResult, HarvestStats,\n RawPAAItem, PAANode, FlatRow, NormalizedQuestionKey, DriverConfig,\n VideoResult, ForumResult, AIOverviewResult, AIModeResult, GoogleSurface,\n WhatPeopleSayingCard, EntityIds, EntityRecord, HarvestDiagnosticWarning, HarvestCompleteness,\n OrganicResult, LocalPackBusiness, BrowserDebugSnapshot, HarvestDebugSnapshot, SerpLocationEvidence,\n} from '../types.js'\nimport type { IPAAExtractor } from './IPAAExtractor.js'\n\nconst DESKTOP_USER_AGENT =\n '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'\n\nconst MOBILE_USER_AGENT =\n 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1'\n\nexport class PAAExtractor implements IPAAExtractor {\n constructor(\n private readonly driver: IBrowserDriver,\n private readonly reporter: IProgressReporter,\n ) {}\n\n private completeness: HarvestCompleteness = {\n paaWithoutAnswer: 0,\n paaWithoutSource: 0,\n paaAnswersRecovered: 0,\n aioShareCaptured: null,\n }\n\n private aioShareUrlEarly: string | null = null\n private collectedRows: FlatRow[] = []\n private partialContext: { seed: string; location: string | null; startMs: number } | null = null\n\n private normalizeQuestion(q: string): NormalizedQuestionKey {\n return q.toLowerCase().replace(/[^\\w\\s]/g, '').replace(/\\s+/g, ' ').trim() as NormalizedQuestionKey\n }\n\n private throwIfAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) return\n if (signal.reason instanceof DOMException && signal.reason.name === 'TimeoutError') throw signal.reason\n throw new RequestAbortedError()\n }\n\n private async throwIfCaptcha(page: Page, context: string): Promise<void> {\n const captchaCount = await page.locator(PAASelectors.captchaMarker).count().catch(() => 0)\n if (captchaCount > 0) {\n throw new CaptchaError(`${context} returned a CAPTCHA — retrying with a fresh session.`)\n }\n }\n\n private async extractVisibleItems(page: Page): Promise<RawPAAItem[]> {\n const sels = PAASelectors\n const raw = await page.evaluate((selectors) => {\n function cleanText(el: Element | null): string {\n if (!el) return ''\n const parts: string[] = []\n for (const n of el.childNodes) {\n if (n.nodeType === Node.TEXT_NODE) {\n const text = n.textContent?.trim()\n if (text) parts.push(text)\n } else if (\n (n as Element).tagName === 'STYLE' ||\n (n as Element).tagName === 'SCRIPT'\n ) {\n continue\n } else {\n const text = cleanText(n as Element)\n if (text) parts.push(text)\n }\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim()\n }\n return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {\n let sourceTitle = (pair.querySelector(selectors.sourceTitle) as HTMLElement | null)?.innerText?.trim() || undefined\n let sourceSite = (pair.querySelector(selectors.sourceSite) as HTMLElement | null)?.innerText?.trim() || undefined\n let sourceCite = (pair.querySelector(selectors.sourceCite) as HTMLElement | null)?.innerText?.trim() || undefined\n if (!sourceCite) {\n const seenHrefs = new Set<string>()\n const anchors: Array<{ text: string; href: string; host: string }> = []\n for (const a of Array.from(pair.querySelectorAll('a[href]')) as HTMLAnchorElement[]) {\n const href = a.href\n if (!/^https?:\\/\\//.test(href) || seenHrefs.has(href)) continue\n let host = ''\n try { host = new URL(href).hostname } catch { continue }\n if (/(^|\\.)google(usercontent)?\\.[a-z.]+$/.test(host)) continue\n seenHrefs.add(href)\n anchors.push({ text: (a.textContent || '').trim(), href, host })\n }\n const primary = anchors.find(x => x.text.length > 2) ?? anchors[0]\n if (primary) {\n sourceTitle = sourceTitle || primary.text || undefined\n sourceSite = sourceSite || primary.host.replace(/^www\\./, '')\n sourceCite = primary.href\n }\n }\n return {\n question:\n pair.getAttribute(selectors.itemDataQ) ||\n pair.getAttribute(selectors.itemDataInitQ) ||\n (pair.querySelector(selectors.itemQuestionEl) as HTMLElement | null)?.innerText?.trim() ||\n '',\n answer: cleanText(pair.querySelector(selectors.answerContainer)) || undefined,\n sourceTitle,\n sourceSite,\n sourceCite,\n }\n })\n }, sels)\n\n return raw.flatMap((item) => {\n const cleaned = {\n ...item,\n answer: cleanPAAAnswerText(item.answer, item.question, item.sourceTitle),\n }\n const result = RawPAAItemSchema.safeParse(cleaned)\n if (!result.success) {\n console.warn('[PAAExtractor] item parse failed:', item.question, result.error.issues[0]?.message)\n return []\n }\n return [result.data]\n })\n }\n\n private async clickItem(page: Page, questionText: string): Promise<void> {\n try {\n const pairLocator = page.locator(\n `${PAASelectors.item}[data-q=\"${questionText}\"], ${PAASelectors.item}[data-initq=\"${questionText}\"]`\n ).first()\n await pairLocator.click()\n } catch { }\n }\n\n private toFlatRow(item: RawPAAItem, depth: number, parentQuestion: string | null, seed: string): FlatRow {\n return {\n seed_query: seed,\n question: item.question,\n answer: item.answer ?? '',\n source_title: item.sourceTitle ?? '',\n source_site: item.sourceSite ?? '',\n source_cite: item.sourceCite ?? '',\n depth,\n parent_question: parentQuestion ?? '',\n extracted_at: new Date().toISOString(),\n }\n }\n\n getPartialResult(): HarvestResult | null {\n const ctx = this.partialContext\n if (!ctx || this.collectedRows.length === 0) return null\n const flat = [...this.collectedRows]\n const stats: HarvestStats = {\n seed: ctx.seed,\n totalQuestions: flat.length,\n maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),\n durationMs: Date.now() - ctx.startMs,\n errorCount: 0,\n }\n return {\n seed: ctx.seed,\n location: ctx.location,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'paa_found',\n problem: null,\n completeness: { ...this.completeness },\n warnings: [{\n code: 'paa_partial',\n surface: 'paa',\n message: `Harvest stopped before finishing; returning the ${flat.length} question(s) collected up to that point.`,\n retryable: true,\n }],\n },\n totalQuestions: flat.length,\n whatPeopleSaying: [],\n tree: this.buildTree(flat, ctx.seed),\n flat,\n videos: [],\n forums: [],\n organicResults: [],\n localPack: [],\n entityIds: {},\n stats,\n } as unknown as HarvestResult\n }\n\n private async runBFS(page: Page, options: HarvestOptions, signal?: AbortSignal): Promise<FlatRow[]> {\n const seenKeys = new Set<NormalizedQuestionKey>()\n const seenQs = new Set<string>()\n const orderedQs: string[] = []\n this.collectedRows = []\n const results: FlatRow[] = this.collectedRows\n const abandonedNeverReclick = new Set<string>()\n const clickedOnceEver = new Set<string>()\n\n const itemArgs = {\n sel: PAASelectors.item,\n dataQ: PAASelectors.itemDataQ,\n dataInitQ: PAASelectors.itemDataInitQ,\n questionEl: PAASelectors.itemQuestionEl,\n answerSel: PAASelectors.answerContainer,\n expandedClass: PAASelectors.expandedClass,\n clickTarget: PAASelectors.clickTarget,\n }\n\n const readItemStates = (): Promise<Array<{ q: string; done: boolean }>> =>\n page.evaluate(\n ({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }: typeof itemArgs) => {\n const answerText = (root: Element | null): string => {\n if (!root) return ''\n let out = ''\n const walk = (n: Node): void => {\n for (const c of Array.from(n.childNodes)) {\n if (c.nodeType === Node.TEXT_NODE) out += c.textContent || ''\n else if (c.nodeType === Node.ELEMENT_NODE && (c as Element).tagName !== 'STYLE' && (c as Element).tagName !== 'SCRIPT') walk(c)\n }\n }\n walk(root)\n return out.replace(/\\s+/g, ' ').trim()\n }\n return Array.from(document.querySelectorAll(sel))\n .map(el => ({\n q:\n el.getAttribute(dataQ) ||\n el.getAttribute(dataInitQ) ||\n (el.querySelector(questionEl) as HTMLElement | null)?.innerText?.trim() ||\n '',\n done: el.classList.contains(expandedClass) || answerText(el.querySelector(answerSel)).length > 30,\n }))\n .filter(s => s.q)\n },\n itemArgs,\n )\n\n const expandOneItemSerially = async (q: string): Promise<'ok' | 'failed'> => {\n const clicked = await page.evaluate(\n ({ sel, dataQ, dataInitQ, clickTarget, q: target }: typeof itemArgs & { q: string }) => {\n const item = Array.from(document.querySelectorAll(sel)).find(el =>\n (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target)\n const btn = item?.querySelector(clickTarget) as HTMLElement | null\n if (!btn) return false\n btn.scrollIntoView({ block: 'center' })\n btn.click()\n return true\n },\n { ...itemArgs, q },\n ).catch(() => false)\n if (!clicked) return 'failed'\n try {\n const outcome = await page.waitForFunction(\n ({ sel, dataQ, dataInitQ, answerSel, q: target }: typeof itemArgs & { q: string }) => {\n const answerText = (root: Element | null): string => {\n if (!root) return ''\n let out = ''\n const walk = (n: Node): void => {\n for (const c of Array.from(n.childNodes)) {\n if (c.nodeType === Node.TEXT_NODE) out += c.textContent || ''\n else if (c.nodeType === Node.ELEMENT_NODE && (c as Element).tagName !== 'STYLE' && (c as Element).tagName !== 'SCRIPT') walk(c)\n }\n }\n walk(root)\n return out.replace(/\\s+/g, ' ').trim()\n }\n const item = Array.from(document.querySelectorAll(sel)).find(el =>\n (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target)\n if (!item) return 'gone'\n const text = answerText(item.querySelector(answerSel))\n if (/an error has occurred/i.test(text.slice(0, 80))) return 'error'\n if (text.length > 30) return 'ok'\n return false\n },\n { ...itemArgs, q },\n { timeout: 6500, polling: 200 },\n )\n return (await outcome.jsonValue()) === 'ok' ? 'ok' : 'failed'\n } catch {\n return 'failed'\n }\n }\n\n let round = 0\n let growthWaits = 0\n while (true) {\n if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break\n this.throwIfAborted(signal)\n\n const states = await readItemStates()\n for (const s of states) {\n if (!seenQs.has(s.q)) { seenQs.add(s.q); orderedQs.push(s.q) }\n }\n\n const kept = new Set(orderedQs.slice(0, options.maxQuestions))\n const clickable = states.filter(s => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q))\n const target = clickable.find(s => !s.done)\n ?? (seenQs.size < options.maxQuestions ? clickable[0] : undefined)\n if (!target) {\n if (seenQs.size >= options.maxQuestions) break\n if (++growthWaits > 1) break\n const grew = await page.waitForFunction(\n ({ sel, min }: { sel: string; min: number }) => document.querySelectorAll(sel).length > min,\n { sel: PAASelectors.item, min: states.length },\n { timeout: 3000 },\n ).catch(() => null)\n if (!grew) break\n continue\n }\n growthWaits = 0\n\n this.reporter.onDepth(++round)\n await this.throwIfCaptcha(page, 'Google PAA expansion')\n clickedOnceEver.add(target.q)\n if ((await expandOneItemSerially(target.q)) === 'failed') abandonedNeverReclick.add(target.q)\n }\n\n const itemMap = new Map((await this.extractVisibleItems(page)).map(i => [i.question, i]))\n await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick)\n\n for (const q of orderedQs) {\n if (results.length >= options.maxQuestions) break\n const key = this.normalizeQuestion(q)\n if (seenKeys.has(key)) continue\n seenKeys.add(key)\n const item = itemMap.get(q)\n if (item) {\n results.push(this.toFlatRow(item, 1, null, options.query))\n this.reporter.onQuestion({ question: item.question, answer: item.answer ?? null, sourceTitle: item.sourceTitle ?? null, sourceSite: item.sourceSite ?? null, sourceCite: item.sourceCite ?? null, depth: 1, parentQuestion: null, children: [] })\n } else {\n results.push(this.toFlatRow({ question: q, answer: undefined, sourceTitle: undefined, sourceSite: undefined, sourceCite: undefined }, 1, null, options.query))\n }\n }\n\n this.completeness.paaWithoutAnswer = results.filter(r => !r.answer).length\n this.completeness.paaWithoutSource = results.filter(r => !r.source_title && !r.source_site && !r.source_cite).length\n\n return results\n }\n\n private async fillIncompleteItems(\n page: Page,\n orderedQs: string[],\n itemMap: Map<string, RawPAAItem>,\n options: HarvestOptions,\n abandonedNeverReclick?: Set<string>,\n ): Promise<void> {\n const kept = new Set(orderedQs.slice(0, options.maxQuestions))\n const missingBefore = new Set(\n [...kept].filter(q => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q)),\n )\n if (missingBefore.size === 0) return\n\n for (let round = 0; round < 2; round++) {\n if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break\n const stillMissing = [...missingBefore].filter(q => !itemMap.get(q)?.answer)\n if (stillMissing.length === 0) break\n\n const reExpanded = await this.reExpandCollapsedItems(page).catch(() => false)\n await page.waitForTimeout(reExpanded ? 900 : 500)\n\n let improved = false\n for (const item of await this.extractVisibleItems(page)) {\n if (!kept.has(item.question)) continue\n const existing = itemMap.get(item.question)\n if (!item.answer || existing?.answer) continue\n itemMap.set(item.question, {\n ...item,\n sourceTitle: item.sourceTitle ?? existing?.sourceTitle,\n sourceSite: item.sourceSite ?? existing?.sourceSite,\n sourceCite: item.sourceCite ?? existing?.sourceCite,\n })\n improved = true\n }\n if (!improved) break\n }\n\n this.completeness.paaAnswersRecovered = [...missingBefore].filter(q => itemMap.get(q)?.answer).length\n }\n\n private async reExpandCollapsedItems(page: Page): Promise<boolean> {\n const unexpandedSel = `${PAASelectors.item}:not(.${PAASelectors.expandedClass}) ${PAASelectors.clickTarget}`\n const count = await page.locator(unexpandedSel).count().catch(() => 0)\n if (count === 0) return false\n for (let i = 0; i < count; i++) {\n try {\n const btn = page.locator(unexpandedSel).first()\n await btn.scrollIntoViewIfNeeded()\n await btn.click({ force: true })\n await page.waitForTimeout(300)\n } catch { }\n }\n return true\n }\n\n private async extractVideos(page: Page): Promise<VideoResult[]> {\n const vsels = VideoSelectors\n return page.evaluate((sels) => {\n const results: Array<{ type: 'video' | 'short_video'; title: string; channel: string; platform: string; duration: string; url: string }> = []\n const containers = Array.from(document.querySelectorAll(sels.container))\n for (const container of containers) {\n const headingEl = container.querySelector(sels.sectionHeading)\n const headingText = headingEl?.textContent?.trim() ?? ''\n const type: 'video' | 'short_video' = headingText.toLowerCase().includes('short') ? 'short_video' : 'video'\n const items = Array.from(container.querySelectorAll(sels.item))\n for (const a of items) {\n const href = (a as HTMLAnchorElement).href\n if (!href || (!href.includes('youtube') && !href.includes('youtu.be'))) continue\n const raw = a.textContent?.trim() ?? ''\n const ytIdx = raw.indexOf('YouTube')\n if (ytIdx === -1) continue\n const title = raw.slice(0, ytIdx).trim()\n const remainder = raw.slice(ytIdx + 7).replace(/^[·\\s·]+/, '')\n const channelMatch = remainder.match(/^([^·\\n]+)/)\n const channel = channelMatch ? channelMatch[1].trim() : ''\n if (title) results.push({ type, title, channel, platform: 'YouTube', duration: '', url: href })\n }\n }\n return results\n }, vsels)\n }\n\n private async extractForums(page: Page): Promise<ForumResult[]> {\n const fsels = ForumSelectors\n return page.evaluate((sels) => {\n const results: Array<{ title: string; source: string; url: string }> = []\n const sections = Array.from(document.querySelectorAll(sels.section))\n const forumSection = sections.find((s) => s.textContent?.includes('Discussions'))\n if (!forumSection) return results\n const items = Array.from(forumSection.querySelectorAll(sels.item))\n for (const a of items) {\n const href = (a as HTMLAnchorElement).href\n if (!href) continue\n const titleEl = a.querySelector(sels.title)\n const sourceEl = a.querySelector(sels.source)\n const title = titleEl?.textContent?.trim() ?? ''\n const source = sourceEl?.textContent?.trim() ?? ''\n if (title) results.push({ title, source, url: href })\n }\n return results\n }, fsels)\n }\n\n private async extractShortVideos(page: Page, shortUrl: string): Promise<VideoResult[]> {\n try {\n await page.goto(shortUrl, { waitUntil: 'domcontentloaded' })\n await page.waitForTimeout(1500)\n } catch {\n return []\n }\n await this.throwIfCaptcha(page, 'Google short video search')\n\n const svSels = {\n item: ShortVideoSelectors.item,\n platforms: [...ShortVideoSelectors.platforms] as string[],\n }\n\n const raw = await page.evaluate((sels) => {\n const seen = new Set<string>()\n const results: Array<{ title: string; channel: string; platform: string; duration: string; url: string }> = []\n const items = Array.from(document.querySelectorAll(sels.item))\n\n const videoHosts = ['youtube.com', 'youtu.be', 'tiktok.com', 'instagram.com', 'facebook.com', 'fb.watch']\n\n const byHref = new Map<string, string[]>()\n for (const a of items) {\n const href = (a as HTMLAnchorElement).href\n if (!href) continue\n if (!videoHosts.some((h) => href.includes(h))) continue\n const text = a.textContent?.trim() ?? ''\n if (!byHref.has(href)) byHref.set(href, [])\n byHref.get(href)!.push(text)\n }\n\n for (const [href, texts] of byHref.entries()) {\n if (seen.has(href)) continue\n seen.add(href)\n\n const duration = texts.find((t) => /^\\d+:\\d+$/.test(t)) ?? ''\n const titleText = texts.find((t) => !/^\\d+:\\d+$/.test(t) && t.length > 5) ?? ''\n if (!titleText) continue\n\n let title = titleText\n let platform = ''\n let channel = ''\n\n for (const p of sels.platforms) {\n let lastIdx = -1\n let search = 0\n while (true) {\n const found = titleText.indexOf(p, search)\n if (found === -1) break\n lastIdx = found\n search = found + 1\n }\n if (lastIdx === -1) continue\n const after = titleText.slice(lastIdx + p.length)\n const isSourceTag = /^[\\s·]/.test(after) || after.trim() === ''\n if (!isSourceTag) continue\n title = titleText.slice(0, lastIdx).trim()\n platform = p\n const stripped = after.replace(/^[\\s·]+/, '')\n const dotIdx = stripped.indexOf('·')\n channel = (dotIdx === -1 ? stripped : stripped.slice(0, dotIdx)).trim()\n break\n }\n\n if (title) results.push({ title, channel, platform, duration, url: href })\n }\n\n return results\n }, svSels)\n\n return raw.map((r) => ({ type: 'short_video' as const, ...r }))\n }\n\n private async extractWhatPeopleSaying(page: Page): Promise<WhatPeopleSayingCard[]> {\n const sels = WhatPeopleSayingSelectors\n return page.evaluate((s) => {\n const section = Array.from(document.querySelectorAll(s.sectionTag))\n .find((el) => el.textContent?.includes(s.sectionHeadingText))\n ?? document.querySelector('.yG4QQe.TBC9ub.NbhJ1c')\n\n if (!section) return []\n\n return Array.from(section.querySelectorAll(s.card)).map((card) => {\n const link = card.querySelector(s.cardLink) as HTMLAnchorElement | null\n const url = link?.href ?? ''\n\n const titleH1 = card.querySelector(s.titleH1)?.textContent?.trim()\n const titleDiv = card.querySelector(s.titleDiv)?.textContent?.trim()\n const title = titleH1 ?? titleDiv ?? ''\n\n const sourceText = card.querySelector(s.source)?.textContent?.trim() ?? ''\n const platformEl = card.querySelector(s.platformBadge)\n const platformText = platformEl?.textContent?.trim() ?? ''\n\n const ytChannel = card.querySelector(s.ytChannel)?.textContent?.trim() ?? ''\n const ytDate = card.querySelector(s.ytDate)?.textContent?.trim() ?? ''\n const authorNote = card.querySelector(s.authorNote)?.textContent?.trim() ?? null\n\n const commentLabelEl = card.querySelector(s.popularCommentLabel)\n let popularComment: string | null = null\n if (commentLabelEl) {\n let next = commentLabelEl.nextSibling\n while (next) {\n const t = next.textContent?.trim()\n if (t) { popularComment = t; break }\n next = next.nextSibling\n }\n }\n\n const allSpans = Array.from(card.querySelectorAll('span'))\n const duration = allSpans.find((s) => /^\\d+:\\d+$/.test(s.textContent?.trim() ?? ''))?.textContent?.trim() ?? null\n const engagementParts = allSpans\n .map((s) => s.textContent?.trim() ?? '')\n .filter((t) =>\n /\\d/.test(t) && (\n t.includes('comment') || t.includes('reaction') ||\n t.includes('view') || t.includes('like') || t.includes('share')\n )\n )\n const engagement = engagementParts[0] ?? ''\n\n const dateCandidates = allSpans\n .map((s) => s.textContent?.trim() ?? '')\n .filter((t) => /\\d+ (day|week|month|year|hour)s? ago|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/.test(t))\n const date = ytDate || (dateCandidates[0] ?? '')\n\n const platform = platformText || (ytChannel ? 'YouTube' : '')\n const source = ytChannel || sourceText\n\n let type: 'reddit' | 'facebook' | 'youtube' | 'instagram' | 'tiktok' | 'news' | 'unknown' = 'unknown'\n const pl = platform.toLowerCase()\n const src = source.toLowerCase()\n const srcRaw = sourceText.toLowerCase()\n if (pl.includes('reddit') || src.startsWith('r/')) type = 'reddit'\n else if (pl.includes('facebook') || srcRaw.includes('facebook')) type = 'facebook'\n else if (pl.includes('instagram') || srcRaw.includes('instagram')) type = 'instagram'\n else if (pl.includes('tiktok') || srcRaw.includes('tiktok')) type = 'tiktok'\n else if (pl.includes('youtube') || !!ytChannel) type = 'youtube'\n else type = 'news'\n\n return { type, title, url, source, platform, popularComment, engagement, date, duration, authorNote }\n })\n }, sels)\n }\n\n private async extractOrganicResults(page: Page): Promise<OrganicResult[]> {\n const sels = OrganicSelectors\n return page.evaluate((s) => {\n const out: Array<{ position: number; title: string; url: string; domain: string; cite: string | null; snippet: string | null; isRedditStyle: boolean; inlineRating: { value: string; count: string } | null }> = []\n let pos = 0\n document.querySelectorAll(s.result).forEach(card => {\n const titleEl = card.querySelector(s.title)\n if (!titleEl) return\n const title = titleEl.textContent?.trim() ?? ''\n const linkEl = titleEl.closest('a') as HTMLAnchorElement | null\n const url = linkEl?.href ?? ''\n if (!title || !url) return\n pos++\n const cite = card.querySelector(s.cite)?.textContent?.trim() ?? null\n const snippet = card.querySelector(s.snippet)?.textContent?.trim() ?? null\n const isRedditStyle = !!card.querySelector(s.redditCite)\n const ratingEl = card.querySelector(s.ratingWrap)\n const inlineRating = ratingEl\n ? { value: ratingEl.querySelector(s.ratingValue)?.textContent?.trim() ?? '', count: ratingEl.querySelector(s.reviewCount)?.textContent?.trim() ?? '' }\n : null\n let domain = ''\n try { domain = new URL(url).hostname.replace(/^www\\./, '') } catch { domain = card.querySelector(s.siteName)?.textContent?.trim() ?? '' }\n out.push({ position: pos, title, url, domain, cite, snippet, isRedditStyle, inlineRating })\n })\n return out\n }, sels)\n }\n\n private async extractLocalPack(page: Page): Promise<LocalPackBusiness[]> {\n const sels = LocalPackSelectors\n return page.evaluate((s) => {\n const out: Array<{ position: number; name: string; cid: string | null; rating: string | null; reviewCount: string | null; metadata: string[]; websiteUrl: string | null; directionsUrl: string | null }> = []\n let container: Element | null = null\n document.querySelectorAll('[role=\"heading\"]').forEach(h => {\n if (!container && h.textContent?.includes(s.headingText)) container = h.closest('[data-hveid]')\n })\n if (!container) return out\n ;(container as Element).querySelectorAll(s.card).forEach((card, i) => {\n const name = card.querySelector(s.name)?.textContent?.trim() ?? ''\n if (!name) return\n const rating = card.querySelector(s.ratingValue)?.textContent?.trim() ?? null\n const reviewRaw = card.querySelector(s.reviewCount)?.textContent?.trim() ?? null\n const reviewCount = reviewRaw ? reviewRaw.replace(/[()]/g, '').trim() : null\n let cid = (card.querySelector('a[data-cid]') as HTMLAnchorElement | null)?.getAttribute('data-cid') ?? null\n if (!cid) {\n for (const link of Array.from(card.querySelectorAll('a[href]')) as HTMLAnchorElement[]) {\n const m1 = link.href.match(/[?&]cid=(\\d+)/)\n if (m1) { cid = m1[1]; break }\n const m2 = link.href.match(/!1s0x[0-9a-f]+:0x([0-9a-f]+)/i)\n if (m2) { try { cid = BigInt('0x' + m2[1]).toString() } catch { } if (cid) break }\n }\n }\n const metadata: string[] = []\n card.querySelectorAll('div, span').forEach(el => {\n const text = Array.from(el.childNodes)\n .filter(n => n.nodeType === 3)\n .map(n => n.textContent?.trim() ?? '')\n .filter(t => t.length > 1 && t.length < 120)\n .join(' ')\n if (text && !metadata.includes(text)) metadata.push(text)\n })\n const links = Array.from(card.querySelectorAll('a[href]')) as HTMLAnchorElement[]\n const directionsUrl = links.find(a => a.href.includes('google.com/maps'))?.href ?? null\n const websiteUrl = links.find(a => !a.href.includes('google.com') && a.href.startsWith('http'))?.href ?? null\n out.push({ position: i + 1, name, cid, rating, reviewCount, metadata, websiteUrl, directionsUrl })\n })\n return out\n }, sels)\n }\n\n private async extractEntityIds(page: Page): Promise<EntityIds> {\n return page.evaluate(() => {\n const kgIds = new Set<string>()\n const cids = new Set<string>()\n const gcids = new Set<string>()\n const recordMap = new Map<string, { name: string; kgId: string | null; cid: string | null; gcid: string | null }>()\n\n function nameFromWrapper(el: Element): string {\n const sel = ['.OSrXXb', '.dbg0pd', '.tzt0oe', '[role=\"heading\"]', 'h3']\n for (const s of sel) {\n const found = el.querySelector(s)\n if (found?.textContent?.trim()) return found.textContent.trim()\n }\n return ''\n }\n\n document.querySelectorAll('[id^=\"pv-/g/\"]').forEach(wrapper => {\n const raw = wrapper.getAttribute('id')\n if (!raw) return\n const kgId = raw.replace('pv-', '')\n kgIds.add(kgId)\n const name = nameFromWrapper(wrapper)\n const cidEl = wrapper.querySelector('a[data-cid]')\n const cid = cidEl?.getAttribute('data-cid') ?? null\n if (cid) cids.add(cid)\n if (name) recordMap.set(kgId, { name, kgId, cid, gcid: null })\n })\n\n document.querySelectorAll('[data-mid]').forEach(el => {\n const mid = el.getAttribute('data-mid')\n if (!mid?.startsWith('/g/')) return\n kgIds.add(mid)\n if (!recordMap.has(mid)) {\n const name = nameFromWrapper(el)\n if (name) recordMap.set(mid, { name, kgId: mid, cid: null, gcid: null })\n }\n })\n\n document.querySelectorAll('.w7Dbne').forEach(card => {\n const cidEl = card.querySelector('a[data-cid]')\n const cid = cidEl?.getAttribute('data-cid') ?? null\n if (!cid) return\n cids.add(cid)\n const name = card.querySelector('.OSrXXb')?.textContent?.trim() ?? ''\n if (!name) return\n const kgIdEl = card.querySelector('[id^=\"pv-/g/\"]')\n const kgId = kgIdEl ? kgIdEl.getAttribute('id')!.replace('pv-', '') : null\n const key = kgId ?? `cid:${cid}`\n if (recordMap.has(key)) {\n const existing = recordMap.get(key)!\n if (!existing.cid) recordMap.set(key, { ...existing, cid })\n } else {\n recordMap.set(key, { name, kgId, cid, gcid: null })\n }\n })\n\n document.querySelectorAll('a[data-cid]').forEach(el => {\n const cid = el.getAttribute('data-cid')\n if (!cid) return\n cids.add(cid)\n const alreadyNamed = [...recordMap.values()].some(r => r.cid === cid)\n if (!alreadyNamed) {\n let node: Element | null = el.parentElement\n let name = ''\n for (let i = 0; i < 8 && node; i++) {\n const h = node.querySelector('.OSrXXb, .dbg0pd, [role=\"heading\"], h3')\n if (h?.textContent?.trim()) { name = h.textContent.trim(); break }\n node = node.parentElement\n }\n if (name) recordMap.set(`cid:${cid}`, { name, kgId: null, cid, gcid: null })\n }\n })\n\n const scriptContent = Array.from(document.querySelectorAll('script:not([src])'))\n .map(s => s.textContent ?? '')\n .filter(t => t.length > 10_000)\n .join('\\n')\n\n for (const m of scriptContent.matchAll(/\\/g\\/[a-zA-Z0-9_-]{5,20}/g)) kgIds.add(m[0])\n for (const m of scriptContent.matchAll(/gcid:[a-zA-Z0-9_]+/g)) gcids.add(m[0])\n for (const m of scriptContent.matchAll(/0x[0-9a-f]+:0x([0-9a-f]+)/gi)) {\n try { cids.add(BigInt('0x' + m[1]).toString()) } catch { }\n }\n\n return { entities: [...recordMap.values()], kgIds: [...kgIds], cids: [...cids], gcids: [...gcids] }\n })\n }\n\n private mergeLocalPackIntoEntities(entityIds: EntityIds, localPack: LocalPackBusiness[]): EntityIds {\n const cidSet = new Set<string>(entityIds.cids)\n const records = entityIds.entities.map(r => ({ ...r }))\n\n for (const biz of localPack) {\n if (!biz.cid) continue\n cidSet.add(biz.cid)\n const nameNorm = biz.name.toLowerCase().trim()\n const byName = records.find(r => r.name.toLowerCase().trim() === nameNorm)\n if (byName) {\n if (!byName.cid) byName.cid = biz.cid\n } else if (!records.find(r => r.cid === biz.cid)) {\n records.push({ name: biz.name, kgId: null, cid: biz.cid, gcid: null })\n }\n }\n\n return { ...entityIds, entities: records, cids: [...cidSet] }\n }\n\n private async extractAISurfaces(page: Page, options?: HarvestOptions): Promise<{\n surface: GoogleSurface\n aiOverview: AIOverviewResult\n aiMode: AIModeResult\n }> {\n const surfaces = await page.evaluate(extractAISurfacesFromDocument, {\n aio: AIOverviewSelectors,\n aim: AIModeSelectors,\n expandWaitMs: 1500,\n })\n\n if (!surfaces.aiOverview.detected) {\n this.completeness.aioShareCaptured = null\n return surfaces\n }\n\n const shareUrl = this.aioShareUrlEarly\n ?? await this.captureAIOverviewShareUrl(page, options).catch(() => null)\n this.completeness.aioShareCaptured = shareUrl !== null\n return {\n ...surfaces,\n aiOverview: { ...surfaces.aiOverview, shareUrl },\n }\n }\n\n private async captureAIOverviewShareUrl(page: Page, options?: HarvestOptions): Promise<string | null> {\n if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null\n\n const clicked = await page.evaluate(() => {\n const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role=\"heading\"],div'))\n .filter(h => (h.textContent || '').trim() === 'AI Overview')\n for (const head of heads) {\n let sec: HTMLElement | null = head as HTMLElement\n for (let up = 0; up < 8 && sec; up++) {\n const btn = sec.querySelector('[aria-label=\"Share\"]') as HTMLElement | null\n if (btn) { btn.scrollIntoView({ block: 'center' }); btn.click(); return true }\n sec = sec.parentElement\n }\n }\n return false\n }).catch(() => false)\n\n if (!clicked) return null\n\n let url: string | null = null\n try {\n const found = await page.waitForFunction(() => {\n const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)\n while (w.nextNode()) {\n const m = (w.currentNode.textContent || '').match(/https:\\/\\/share\\.google\\/aimode\\/\\S+/)\n if (m) return m[0]\n }\n return false\n }, undefined, { timeout: 4500, polling: 250 })\n url = (await found.jsonValue()) as string\n } catch {\n url = null\n }\n\n await page.keyboard.press('Escape').catch(() => {})\n return url\n }\n\n private buildTree(flat: FlatRow[], _seed: string): PAANode[] {\n const roots: PAANode[] = []\n const nodeMap = new Map<string, PAANode>()\n\n for (const row of flat) {\n const node: PAANode = {\n question: row.question,\n answer: row.answer || null,\n sourceTitle: row.source_title || null,\n sourceSite: row.source_site || null,\n sourceCite: row.source_cite || null,\n depth: row.depth,\n parentQuestion: row.parent_question || null,\n children: [],\n }\n nodeMap.set(row.question, node)\n }\n\n for (const node of nodeMap.values()) {\n if (node.parentQuestion && nodeMap.has(node.parentQuestion)) {\n nodeMap.get(node.parentQuestion)!.children.push(node)\n } else {\n roots.push(node)\n }\n }\n\n return roots\n }\n\n private getBrowserDebugSnapshot(): BrowserDebugSnapshot {\n return this.driver.getDebugSnapshot()\n }\n\n private buildHarvestDebugSnapshot(\n options: HarvestOptions,\n canonicalLocation: string | null,\n uule: string | null,\n locationEvidence?: SerpLocationEvidence,\n ): HarvestDebugSnapshot | undefined {\n if (!options.debug) return undefined\n return {\n enabled: true,\n request: {\n query: options.query,\n locationInput: options.location ?? null,\n canonicalLocation,\n uule,\n gl: options.gl,\n hl: options.hl,\n device: options.device,\n proxyMode: options.proxyMode,\n proxyZip: options.proxyZip ?? null,\n serpOnly: options.serpOnly,\n pages: options.pages ?? 1,\n },\n browser: this.getBrowserDebugSnapshot(),\n ...(locationEvidence ? { locationEvidence } : {}),\n }\n }\n\n async extract(options: HarvestOptions, signal?: AbortSignal): Promise<HarvestResult> {\n const startMs = Date.now()\n this.completeness = { paaWithoutAnswer: 0, paaWithoutSource: 0, paaAnswersRecovered: 0, aioShareCaptured: null }\n this.aioShareUrlEarly = null\n this.collectedRows = []\n this.partialContext = { seed: options.query, location: options.location ?? null, startMs }\n const isMobile = options.device === 'mobile'\n const config: DriverConfig = {\n headless: options.headless,\n profileDir: options.profileDir,\n proxy: options.proxy,\n kernelApiKey: options.kernelApiKey,\n kernelProxyId: options.kernelProxyId,\n kernelProxyResolution: options.kernelProxyResolution as DriverConfig['kernelProxyResolution'],\n proxyMode: options.proxyMode,\n keepDefaultProxy: options.keepDefaultProxy,\n requireUsEgress: options.requireUsEgress,\n viewport: isMobile ? { width: 390, height: 844 } : { width: 1280, height: 800 },\n locale: `${options.hl}-${options.gl.toUpperCase()}`,\n userAgent: isMobile ? MOBILE_USER_AGENT : DESKTOP_USER_AGENT,\n deviceScaleFactor: isMobile ? 3 : 1,\n isMobile,\n hasTouch: isMobile,\n debug: options.debug,\n }\n\n let errorCount = 0\n const diagnosticWarnings: HarvestDiagnosticWarning[] = []\n\n try {\n this.throwIfAborted(signal)\n await this.driver.launch(config)\n this.throwIfAborted(signal)\n const canonicalLocation = options.location ? normalizeLocation(options.location) : null\n const uule = canonicalLocation ? encodeUule(canonicalLocation) : null\n const executedQuery = buildLocalizedSerpQuery(options.query, options.location, canonicalLocation)\n const executionOptions = executedQuery === options.query ? options : { ...options, query: executedQuery }\n const { hasPaa } = await this.driver.navigateToSERP(\n executionOptions.query,\n uule,\n options.gl,\n options.hl,\n {\n ...(options.serpOnly ? { num: 100 } : {}),\n ...(recencyToTbs(options.recency) ? { tbs: recencyToTbs(options.recency) } : {}),\n debug: options.debug,\n },\n )\n this.throwIfAborted(signal)\n\n const page = this.driver.getPage() as Page\n await this.throwIfCaptcha(page, 'Google SERP')\n\n this.aioShareUrlEarly = await this.captureAIOverviewShareUrl(page, options).catch(() => null)\n\n if (options.serpOnly) {\n const [organicResults, localPack, rawEntityIds] = await Promise.all([\n this.extractOrganicResults(page),\n this.extractLocalPack(page),\n this.extractEntityIds(page),\n ])\n const entityIds = this.mergeLocalPackIntoEntities(rawEntityIds, localPack)\n const aiSurfaces = await this.extractAISurfaces(page, options)\n let locationEvidence = options.debug\n ? inferSerpLocationEvidence(canonicalLocation, organicResults, localPack)\n : undefined\n let allOrganic = organicResults\n if ((options.pages ?? 1) >= 2) {\n const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', start: '10' })\n if (recencyToTbs(options.recency)) p2params.set('tbs', recencyToTbs(options.recency) as string)\n if (uule) p2params.set('uule', uule)\n await this.driver.navigateTo('https://www.google.com/search?' + p2params.toString())\n await this.throwIfCaptcha(page, 'Google SERP page 2')\n const p2organic = await this.extractOrganicResults(page)\n allOrganic = [...organicResults, ...p2organic.map(r => ({ ...r, position: r.position + 10 }))]\n if (options.debug) {\n locationEvidence = inferSerpLocationEvidence(canonicalLocation, allOrganic, localPack)\n }\n }\n const stats: HarvestStats = {\n seed: executionOptions.query, totalQuestions: 0, maxDepthReached: 0,\n durationMs: Date.now() - startMs, errorCount,\n }\n this.reporter.onComplete(stats)\n return {\n seed: executionOptions.query, location: options.location ?? null,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'serp_only',\n problem: null,\n completeness: { ...this.completeness },\n ...(options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}),\n },\n totalQuestions: 0,\n surface: aiSurfaces.surface,\n aiOverview: aiSurfaces.aiOverview,\n aiMode: aiSurfaces.aiMode,\n whatPeopleSaying: [], tree: [], flat: [], videos: [], forums: [],\n organicResults: allOrganic, localPack, entityIds, stats,\n }\n }\n\n const [videos, forums, whatPeopleSaying, rawEntityIds, organicResults, localPack] = await Promise.all([\n this.extractVideos(page),\n this.extractForums(page),\n this.extractWhatPeopleSaying(page),\n this.extractEntityIds(page),\n this.extractOrganicResults(page),\n this.extractLocalPack(page),\n ])\n const entityIds = this.mergeLocalPackIntoEntities(rawEntityIds, localPack)\n const initialLocationEvidence = options.debug\n ? inferSerpLocationEvidence(canonicalLocation, organicResults, localPack)\n : undefined\n this.reporter.onVideos(videos)\n this.reporter.onForums(forums)\n\n if (!hasPaa) {\n let noPaaOrganic = organicResults\n let locationEvidence = initialLocationEvidence\n if ((options.pages ?? 1) >= 2) {\n const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', start: '10' })\n if (recencyToTbs(options.recency)) p2params.set('tbs', recencyToTbs(options.recency) as string)\n if (uule) p2params.set('uule', uule)\n await this.driver.navigateTo('https://www.google.com/search?' + p2params.toString())\n await this.throwIfCaptcha(page, 'Google SERP page 2')\n const p2organic = await this.extractOrganicResults(page)\n noPaaOrganic = [...organicResults, ...p2organic.map(r => ({ ...r, position: r.position + 10 }))]\n if (options.debug) {\n locationEvidence = inferSerpLocationEvidence(canonicalLocation, noPaaOrganic, localPack)\n }\n }\n const aiSurfaces = await this.extractAISurfaces(page, options)\n const stats: HarvestStats = {\n seed: executionOptions.query, totalQuestions: 0, maxDepthReached: 0,\n durationMs: Date.now() - startMs, errorCount,\n }\n this.reporter.onComplete(stats)\n return {\n seed: executionOptions.query, location: options.location ?? null,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'no_paa',\n problem: null,\n completeness: { ...this.completeness },\n ...(options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}),\n },\n totalQuestions: 0,\n surface: aiSurfaces.surface,\n aiOverview: aiSurfaces.aiOverview,\n aiMode: aiSurfaces.aiMode,\n whatPeopleSaying, tree: [], flat: [], videos, forums,\n organicResults: noPaaOrganic, localPack, entityIds, stats,\n }\n }\n\n const flat = await this.runBFS(page, executionOptions, signal)\n this.throwIfAborted(signal)\n const aiSurfaces = await this.extractAISurfaces(page, options)\n\n const shortVidsParams = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', udm: ShortVideoSelectors.udm })\n if (uule) shortVidsParams.set('uule', uule)\n let shortVideos: VideoResult[] = []\n try {\n shortVideos = await this.extractShortVideos(page, 'https://www.google.com/search?' + shortVidsParams.toString())\n } catch (err) {\n if (!(err instanceof CaptchaError)) throw err\n errorCount++\n diagnosticWarnings.push({\n code: 'short_videos_captcha_skipped',\n surface: 'short_videos',\n message: err.message,\n retryable: true,\n })\n }\n this.reporter.onVideos(shortVideos)\n\n let allOrganic = organicResults\n let locationEvidence = initialLocationEvidence\n if ((options.pages ?? 1) >= 2) {\n const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', start: '10' })\n if (recencyToTbs(options.recency)) p2params.set('tbs', recencyToTbs(options.recency) as string)\n if (uule) p2params.set('uule', uule)\n await this.driver.navigateTo('https://www.google.com/search?' + p2params.toString())\n await this.throwIfCaptcha(page, 'Google SERP page 2')\n const p2organic = await this.extractOrganicResults(page)\n allOrganic = [...organicResults, ...p2organic.map(r => ({ ...r, position: r.position + 10 }))]\n if (options.debug) {\n locationEvidence = inferSerpLocationEvidence(canonicalLocation, allOrganic, localPack)\n }\n }\n\n const allVideos = [...videos, ...shortVideos]\n const tree = this.buildTree(flat, executionOptions.query)\n\n const stats: HarvestStats = {\n seed: executionOptions.query,\n totalQuestions: flat.length,\n maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),\n durationMs: Date.now() - startMs,\n errorCount,\n }\n\n this.reporter.onComplete(stats)\n\n return {\n seed: executionOptions.query,\n location: options.location ?? null,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'paa_found',\n problem: null,\n completeness: { ...this.completeness },\n ...(diagnosticWarnings.length > 0 ? { warnings: diagnosticWarnings } : {}),\n ...(options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}),\n },\n totalQuestions: flat.length,\n surface: aiSurfaces.surface,\n aiOverview: aiSurfaces.aiOverview,\n aiMode: aiSurfaces.aiMode,\n whatPeopleSaying,\n tree,\n flat,\n videos: allVideos,\n forums,\n organicResults: allOrganic,\n localPack,\n entityIds,\n stats,\n }\n } catch (err) {\n errorCount++\n this.reporter.onError(err instanceof Error ? err : new Error(String(err)))\n throw err\n }\n }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport Papa from 'papaparse'\nimport type { HarvestResult, FlatRow, VideoResult, ForumResult, AICitation, WhatPeopleSayingCard } from '../types.js'\nimport type { IOutputSerializer } from './IOutputSerializer.js'\nimport { postToMemoryLibrary } from './memory-library-sink.js'\n\nexport class OutputSerializer implements IOutputSerializer {\n async writeJSON(result: HarvestResult, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = result.seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const filename = `${slug}-${Date.now()}.json`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, JSON.stringify(result, null, 2), 'utf8')\n await postToMemoryLibrary({ title: `${result.seed} scrape`, content: JSON.stringify(result), source: `mcp-scraper:${fullPath}` })\n return fullPath\n }\n\n async writeCSV(rows: FlatRow[], outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const seedRaw = rows[0]?.seed_query ?? 'paa'\n const slug = seedRaw.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeVideoCSV(videos: VideoResult[], seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const csv = Papa.unparse(videos, { header: true })\n const filename = `${slug}-videos-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeForumCSV(forums: ForumResult[], seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const csv = Papa.unparse(forums, { header: true })\n const filename = `${slug}-forums-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeAIOverviewCSV(citations: AICitation[], text: string | null, seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const rows = citations.map((c, i) => ({\n seed_query: seed,\n response_text: i === 0 ? (text ?? '') : '',\n citation_text: c.text,\n citation_href: c.href,\n }))\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-ai-overview-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeAIModeCSV(citations: AICitation[], text: string | null, seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const rows = citations.map((c, i) => ({\n seed_query: seed,\n response_text: i === 0 ? (text ?? '') : '',\n citation_text: c.text,\n citation_href: c.href,\n }))\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-ai-mode-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeWhatPeopleSayingCSV(cards: WhatPeopleSayingCard[], seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const rows = cards.map((c) => ({ seed_query: seed, ...c }))\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-what-people-saying-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n}\n","export async function postToMemoryLibrary(args: { title: string; content: string; source: string }): Promise<void> {\n const url = process.env.MCP_MEMORY_INGEST_URL\n const key = process.env.MCP_MEMORY_KEY\n if (!url || !key) return\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ data: { apiKey: key, title: args.title, content: args.content, source: args.source } }),\n })\n const body = (await res.json().catch(() => null)) as { result?: { error?: boolean; message?: string } } | null\n if (!res.ok || body?.result?.error) {\n console.warn('[memory-library-sink] ingest not accepted:', res.status, body?.result?.message ?? '')\n }\n } catch (err) {\n console.warn('[memory-library-sink] ingest failed:', (err as Error)?.message)\n }\n}\n\nexport function memoryLibrarySinkEnabled(): boolean {\n return Boolean(process.env.MCP_MEMORY_INGEST_URL && process.env.MCP_MEMORY_KEY)\n}\n","import type { PAANode, HarvestStats, VideoResult, ForumResult } from '../types.js'\nimport type { IProgressReporter } from './IProgressReporter.js'\n\nexport class ProgressReporter implements IProgressReporter {\n onQuestion(node: PAANode): void {\n process.stdout.write(JSON.stringify({ event: 'question', depth: node.depth, question: node.question }) + '\\n')\n }\n\n onDepth(depth: number): void {\n process.stdout.write(JSON.stringify({ event: 'depth', depth }) + '\\n')\n }\n\n onVideos(videos: VideoResult[]): void {\n for (const v of videos) {\n process.stdout.write(JSON.stringify({ event: 'video', type: v.type, platform: v.platform, duration: v.duration, title: v.title, channel: v.channel, url: v.url }) + '\\n')\n }\n }\n\n onForums(forums: ForumResult[]): void {\n for (const f of forums) {\n process.stdout.write(JSON.stringify({ event: 'forum', title: f.title, source: f.source, url: f.url }) + '\\n')\n }\n }\n\n onComplete(stats: HarvestStats): void {\n process.stdout.write(JSON.stringify({ event: 'complete', ...stats }) + '\\n')\n }\n\n onError(err: Error): void {\n process.stderr.write(JSON.stringify({ event: 'error', type: err.constructor.name, message: err.message }) + '\\n')\n }\n}\n","import Kernel from '@onkernel/sdk'\nimport { backupProxyAvailable, createBackupProxyId, scrubBackupProxyText } from './backup-proxy.js'\nimport { normalizeLocation } from './uule.js'\nimport type { CanonicalLocationName, KernelLocationProxyTarget, KernelProxyResolutionDebug, ProxyMode } from './types.js'\n\ntype KernelProxyConfig = {\n country?: string\n state?: string\n city?: string\n zip?: string\n}\n\ntype KernelProxyListItem = {\n id?: string\n name?: string\n type?: string\n status?: string\n config?: KernelProxyConfig\n}\n\ntype KernelProxyCreateResponse = {\n id?: string\n name?: string\n type?: string\n status?: string\n config?: KernelProxyConfig\n}\n\nexport interface ResolveKernelProxyOptions {\n kernelApiKey?: string\n proxyMode: ProxyMode\n configuredKernelProxyId?: string\n location?: string\n proxyZip?: string\n gl: string\n attemptIndex?: number\n fresh?: boolean\n backupOnExhaustion?: boolean\n}\n\nexport interface ResolveKernelProxyResult {\n kernelProxyId?: string\n resolution: KernelProxyResolutionDebug\n disposableProxyId?: string\n}\n\nconst US_STATE_CODES: Record<string, string> = {\n alabama: 'AL',\n alaska: 'AK',\n arizona: 'AZ',\n arkansas: 'AR',\n california: 'CA',\n colorado: 'CO',\n connecticut: 'CT',\n delaware: 'DE',\n florida: 'FL',\n georgia: 'GA',\n hawaii: 'HI',\n idaho: 'ID',\n illinois: 'IL',\n indiana: 'IN',\n iowa: 'IA',\n kansas: 'KS',\n kentucky: 'KY',\n louisiana: 'LA',\n maine: 'ME',\n maryland: 'MD',\n massachusetts: 'MA',\n michigan: 'MI',\n minnesota: 'MN',\n mississippi: 'MS',\n missouri: 'MO',\n montana: 'MT',\n nebraska: 'NE',\n nevada: 'NV',\n 'new hampshire': 'NH',\n 'new jersey': 'NJ',\n 'new mexico': 'NM',\n 'new york': 'NY',\n 'north carolina': 'NC',\n 'north dakota': 'ND',\n ohio: 'OH',\n oklahoma: 'OK',\n oregon: 'OR',\n pennsylvania: 'PA',\n 'rhode island': 'RI',\n 'south carolina': 'SC',\n 'south dakota': 'SD',\n tennessee: 'TN',\n texas: 'TX',\n utah: 'UT',\n vermont: 'VT',\n virginia: 'VA',\n washington: 'WA',\n 'west virginia': 'WV',\n wisconsin: 'WI',\n wyoming: 'WY',\n}\n\nconst US_CITY_CENTER_ZIPS: Record<string, string> = {\n 'atlanta|GA': '30303',\n 'austin|TX': '78701',\n 'baltimore|MD': '21201',\n 'boston|MA': '02108',\n 'boulder|CO': '80302',\n 'charlotte|NC': '28202',\n 'chicago|IL': '60601',\n 'colorado_springs|CO': '80903',\n 'columbus|OH': '43215',\n 'dallas|TX': '75201',\n 'denver|CO': '80202',\n 'detroit|MI': '48226',\n 'fort_collins|CO': '80524',\n 'fort_worth|TX': '76102',\n 'houston|TX': '77002',\n 'indianapolis|IN': '46204',\n 'jacksonville|FL': '32202',\n 'las_vegas|NV': '89101',\n 'los_angeles|CA': '90012',\n 'louisville|KY': '40202',\n 'loveland|CO': '80537',\n 'memphis|TN': '38103',\n 'miami|FL': '33131',\n 'minneapolis|MN': '55401',\n 'nashville|TN': '37203',\n 'new_york|NY': '10001',\n 'orlando|FL': '32801',\n 'philadelphia|PA': '19103',\n 'phoenix|AZ': '85004',\n 'portland|OR': '97205',\n 'raleigh|NC': '27601',\n 'richmond|VA': '23219',\n 'sacramento|CA': '95814',\n 'salt_lake_city|UT': '84101',\n 'san_antonio|TX': '78205',\n 'san_diego|CA': '92101',\n 'san_francisco|CA': '94103',\n 'san_jose|CA': '95113',\n 'seattle|WA': '98101',\n}\n\nfunction proxyIdSuffix(proxyId: string | null | undefined): string | null {\n return proxyId ? proxyId.slice(-6) : null\n}\n\nfunction resolution(\n source: KernelProxyResolutionDebug['source'],\n proxyMode: ProxyMode,\n proxyId: string | undefined,\n target: KernelLocationProxyTarget | null,\n error: string | null,\n disposable = false,\n): ResolveKernelProxyResult {\n const proxyType = source === 'disabled' ? 'none'\n : source === 'configured_fallback' ? 'configured'\n : source === 'isp_created' ? 'isp'\n : source === 'backup_custom' ? 'custom'\n : source === 'location_created' || source === 'location_reused' ? 'residential'\n : 'unknown'\n return {\n kernelProxyId: proxyId,\n ...(disposable && proxyId ? { disposableProxyId: proxyId } : {}),\n resolution: {\n source,\n proxyType,\n proxyMode,\n proxyIdPresent: Boolean(proxyId),\n proxyIdSuffix: proxyIdSuffix(proxyId),\n target,\n error,\n disposable,\n },\n }\n}\n\nfunction normalizeStateName(value: string): string {\n return value.trim().toLowerCase().replace(/\\s+/g, ' ')\n}\n\nfunction normalizeCountryName(value: string): string {\n return value.trim().toLowerCase().replace(/\\./g, '').replace(/\\s+/g, ' ')\n}\n\nfunction isUnitedStates(country: string | undefined): boolean {\n if (!country) return true\n const normalized = normalizeCountryName(country)\n return normalized === 'united states' || normalized === 'united states of america' || normalized === 'usa' || normalized === 'us'\n}\n\nfunction stateCodeFor(region: string): string | null {\n const trimmed = region.trim()\n if (/^[A-Za-z]{2}$/.test(trimmed)) return trimmed.toUpperCase()\n return US_STATE_CODES[normalizeStateName(trimmed)] ?? null\n}\n\nexport function kernelCityIdentifierCandidates(city: string): string[] {\n const ascii = city\n .normalize('NFKD')\n .replace(/[^\\x00-\\x7F]/g, '')\n .toLowerCase()\n const words = ascii.split(/[^a-z0-9]+/).filter(Boolean)\n const underscored = words.join('_')\n const compact = words.join('')\n return Array.from(new Set([underscored, compact].filter(Boolean)))\n}\n\nfunction proxyName(country: string, state: string, city?: string): string {\n return city\n ? `mcp-serp-residential-${country.toLowerCase()}-${state.toLowerCase()}-${city}`\n : `mcp-serp-residential-${country.toLowerCase()}-${state.toLowerCase()}`\n}\n\nfunction freshProxyName(baseName: string, attemptIndex: number | undefined): string {\n const stamp = `${Date.now()}-${attemptIndex ?? 0}-${Math.random().toString(36).slice(2, 8)}`\n return `${baseName}-fresh-${stamp}`\n}\n\nfunction zipProxyName(zip: string): string {\n return `mcp-serp-residential-us-zip-${zip}`\n}\n\nexport function parseKernelLocationProxyTarget(location: string | undefined, gl: string): KernelLocationProxyTarget | null {\n if (!location || gl.toLowerCase() !== 'us') return null\n const canonicalLocation = normalizeLocation(location)\n let parts = canonicalLocation.split(',').map(part => part.trim()).filter(Boolean)\n if (parts.length > 1 && isUnitedStates(parts[parts.length - 1])) {\n parts = parts.slice(0, -1)\n }\n\n if (parts.length === 1) {\n const stateOnly = stateCodeFor(parts[0])\n if (!stateOnly) return null\n return {\n canonicalLocation: canonicalLocation as CanonicalLocationName,\n level: 'state',\n country: 'US',\n state: stateOnly,\n city: '',\n cityCandidates: [],\n proxyName: proxyName('US', stateOnly),\n config: {\n country: 'US',\n state: stateOnly,\n },\n }\n }\n\n const [city = '', region = ''] = parts\n if (!city || !region) return null\n const state = stateCodeFor(region)\n if (!state) return null\n const cityCandidates = kernelCityIdentifierCandidates(city)\n const primaryCity = cityCandidates[0]\n if (!primaryCity) return null\n return {\n canonicalLocation: canonicalLocation as CanonicalLocationName,\n level: 'city',\n country: 'US',\n state,\n city: primaryCity,\n cityCandidates,\n proxyName: proxyName('US', state, primaryCity),\n config: {\n country: 'US',\n state,\n city: primaryCity,\n },\n }\n}\n\nfunction cityZipKey(target: KernelLocationProxyTarget): string {\n return `${target.city}|${target.state}`\n}\n\nfunction knownZipFor(target: KernelLocationProxyTarget, explicitZip: string | undefined): string | null {\n if (explicitZip && /^\\d{5}$/.test(explicitZip)) return explicitZip\n return US_CITY_CENTER_ZIPS[cityZipKey(target)] ?? null\n}\n\nfunction zipTarget(target: KernelLocationProxyTarget, zip: string): KernelLocationProxyTarget {\n return {\n ...target,\n level: 'zip',\n zip,\n proxyName: zipProxyName(zip),\n config: {\n country: target.country,\n state: target.state,\n zip,\n },\n }\n}\n\nfunction withProxyName(target: KernelLocationProxyTarget, name: string): KernelLocationProxyTarget {\n return {\n ...target,\n proxyName: name,\n }\n}\n\nfunction configMatches(config: KernelProxyConfig | undefined, target: KernelLocationProxyTarget, city?: string): boolean {\n if (target.level === 'zip') {\n return config?.country?.toUpperCase() === target.country && config?.zip === target.zip\n }\n return config?.country?.toUpperCase() === target.country &&\n config?.state?.toUpperCase() === target.state &&\n (city ? config?.city === city : !config?.city)\n}\n\nfunction findExistingTargetProxy(proxies: KernelProxyListItem[], target: KernelLocationProxyTarget): KernelProxyListItem | null {\n return proxies.find(proxy => (\n proxy.type === 'residential' &&\n proxy.status !== 'unavailable' &&\n Boolean(proxy.id) &&\n (proxy.name === target.proxyName || configMatches(proxy.config, target, target.level === 'city' ? target.city : undefined))\n )) ?? null\n}\n\nfunction findExistingProxy(proxies: KernelProxyListItem[], target: KernelLocationProxyTarget): KernelProxyListItem | null {\n for (const city of target.cityCandidates) {\n const name = proxyName(target.country, target.state, city)\n const found = proxies.find(proxy => (\n proxy.type === 'residential' &&\n proxy.status !== 'unavailable' &&\n Boolean(proxy.id) &&\n (proxy.name === name || configMatches(proxy.config, target, city))\n ))\n if (found) return found\n }\n return null\n}\n\nfunction stateTarget(target: KernelLocationProxyTarget): KernelLocationProxyTarget {\n return {\n ...target,\n level: 'state',\n proxyName: proxyName(target.country, target.state),\n config: {\n country: target.country,\n state: target.state,\n },\n }\n}\n\nfunction findExistingStateProxy(proxies: KernelProxyListItem[], target: KernelLocationProxyTarget): KernelProxyListItem | null {\n const name = proxyName(target.country, target.state)\n return proxies.find(proxy => (\n proxy.type === 'residential' &&\n proxy.status !== 'unavailable' &&\n Boolean(proxy.id) &&\n (proxy.name === name || configMatches(proxy.config, target))\n )) ?? null\n}\n\nfunction escalatedTargetLevel(target: KernelLocationProxyTarget, attemptIndex: number): KernelLocationProxyTarget {\n return stateTarget(target)\n}\n\nfunction errorText(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction freshTargetCandidates(\n target: KernelLocationProxyTarget,\n explicitZip: string | undefined,\n attemptIndex: number | undefined,\n): KernelLocationProxyTarget[] {\n const out: KernelLocationProxyTarget[] = []\n const zip = knownZipFor(target, explicitZip)\n if (zip) {\n const targetZip = zipTarget(target, zip)\n out.push(withProxyName(targetZip, freshProxyName(targetZip.proxyName, attemptIndex)))\n }\n\n for (const city of target.cityCandidates) {\n const cityTarget: KernelLocationProxyTarget = {\n ...target,\n level: 'city',\n city,\n proxyName: proxyName(target.country, target.state, city),\n config: {\n country: target.country,\n state: target.state,\n city,\n },\n }\n out.push(withProxyName(cityTarget, freshProxyName(cityTarget.proxyName, attemptIndex)))\n }\n\n const fallbackTarget = stateTarget(target)\n out.push(withProxyName(fallbackTarget, freshProxyName(fallbackTarget.proxyName, attemptIndex)))\n return out\n}\n\nasync function settleCreateExhaustion(\n options: ResolveKernelProxyOptions,\n target: KernelLocationProxyTarget | null,\n createErrors: string[],\n): Promise<ResolveKernelProxyResult> {\n if (options.backupOnExhaustion && createErrors.length > 0 && options.kernelApiKey && backupProxyAvailable()) {\n try {\n const backupId = await createBackupProxyId(options.kernelApiKey, { attemptIndex: options.attemptIndex })\n return resolution('backup_custom', options.proxyMode, backupId, target, null, true)\n } catch (err) {\n createErrors.push(`backup: ${scrubBackupProxyText(errorText(err))}`)\n }\n }\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(' | '))\n}\n\nexport async function resolveBackupKernelProxyId(\n options: Pick<ResolveKernelProxyOptions, 'kernelApiKey' | 'proxyMode' | 'attemptIndex'>,\n): Promise<ResolveKernelProxyResult | null> {\n if (!options.kernelApiKey || !backupProxyAvailable()) return null\n try {\n const backupId = await createBackupProxyId(options.kernelApiKey, { attemptIndex: options.attemptIndex })\n return resolution('backup_custom', options.proxyMode, backupId, null, null, true)\n } catch {\n return null\n }\n}\n\nasync function createFreshLocationProxy(\n kernel: Kernel,\n options: ResolveKernelProxyOptions,\n target: KernelLocationProxyTarget,\n): Promise<ResolveKernelProxyResult> {\n const createErrors: string[] = []\n\n for (const candidate of freshTargetCandidates(target, options.proxyZip, options.attemptIndex)) {\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: candidate.proxyName,\n config: candidate.level === 'zip'\n ? { country: candidate.country, zip: candidate.zip }\n : candidate.config,\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, candidate, null, true)\n }\n createErrors.push(`${candidate.proxyName}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${candidate.proxyName}: ${errorText(err)}`)\n }\n }\n\n if (process.env.KERNEL_PROXY_TYPE !== 'residential') {\n const ispTarget = stateTarget(target)\n try {\n const created = await kernel.proxies.create({\n type: 'isp',\n name: freshProxyName(`mcp-serp-isp-${ispTarget.country.toLowerCase()}-${(ispTarget.state ?? '').toLowerCase()}`, options.attemptIndex),\n config: { country: ispTarget.country, ...(ispTarget.state ? { state: ispTarget.state } : {}) },\n } as never) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('isp_created', options.proxyMode, created.id, ispTarget, null, true)\n }\n createErrors.push('isp: Kernel did not return a proxy id')\n } catch (err) {\n createErrors.push(`isp: ${errorText(err)}`)\n }\n }\n\n return settleCreateExhaustion(options, target, createErrors)\n}\n\nexport async function deleteKernelProxyId(kernelApiKey: string | undefined, proxyId: string | undefined): Promise<void> {\n if (!kernelApiKey || !proxyId) return\n const kernel = new Kernel({ apiKey: kernelApiKey })\n await kernel.proxies.delete(proxyId)\n}\n\nexport async function createFreshUsIspProxy(kernelApiKey: string, attemptIndex = 0): Promise<string> {\n const kernel = new Kernel({ apiKey: kernelApiKey })\n const created = await kernel.proxies.create({\n type: 'isp',\n name: freshProxyName('mcp-serp-isp-us', attemptIndex),\n config: { country: 'US' },\n } as never) as KernelProxyCreateResponse\n if (!created.id) throw new Error('Kernel did not return a US ISP proxy id')\n return created.id\n}\n\nexport async function resolveKernelProxyId(options: ResolveKernelProxyOptions): Promise<ResolveKernelProxyResult> {\n if (options.proxyMode === 'none') {\n return resolution('disabled', options.proxyMode, undefined, null, null)\n }\n\n if (options.proxyMode === 'configured') {\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, null, null)\n }\n\n const target = parseKernelLocationProxyTarget(options.location, options.gl)\n if (!target || !options.kernelApiKey) {\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, target, target ? null : 'location could not be normalized to a US city/state proxy target')\n }\n\n const kernel = new Kernel({ apiKey: options.kernelApiKey })\n try {\n const attemptIndex = options.attemptIndex ?? 0\n\n if (options.fresh) {\n return await createFreshLocationProxy(kernel, options, target)\n }\n\n if (attemptIndex >= 1) {\n const escalatedTarget = escalatedTargetLevel(target, attemptIndex)\n const createErrors: string[] = []\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: escalatedTarget.proxyName,\n config: escalatedTarget.config,\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, escalatedTarget, null)\n }\n createErrors.push(`${escalatedTarget.state}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${escalatedTarget.state}: ${errorText(err)}`)\n }\n return settleCreateExhaustion(options, escalatedTarget, createErrors)\n }\n\n const proxies = await kernel.proxies.list() as KernelProxyListItem[]\n const zip = knownZipFor(target, options.proxyZip)\n const createErrors: string[] = []\n if (zip) {\n const targetZip = zipTarget(target, zip)\n const existingZip = findExistingTargetProxy(proxies, targetZip)\n if (existingZip?.id) {\n return resolution('location_reused', options.proxyMode, existingZip.id, targetZip, null)\n }\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: targetZip.proxyName,\n config: {\n country: targetZip.country,\n zip,\n },\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, targetZip, null)\n }\n createErrors.push(`${zip}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${zip}: ${errorText(err)}`)\n }\n }\n\n const existing = findExistingProxy(proxies, target)\n if (existing?.id) {\n return resolution('location_reused', options.proxyMode, existing.id, target, createErrors.join(' | ') || null)\n }\n\n for (const city of target.cityCandidates) {\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: proxyName(target.country, target.state, city),\n config: {\n country: target.country,\n state: target.state,\n city,\n },\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, {\n ...target,\n level: 'city',\n city,\n proxyName: proxyName(target.country, target.state, city),\n config: {\n country: target.country,\n state: target.state,\n city,\n },\n }, null)\n }\n createErrors.push(`${city}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${city}: ${errorText(err)}`)\n }\n }\n const fallbackTarget = stateTarget(target)\n const existingState = findExistingStateProxy(proxies, fallbackTarget)\n if (existingState?.id) {\n return resolution('location_reused', options.proxyMode, existingState.id, fallbackTarget, createErrors.join(' | '))\n }\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: fallbackTarget.proxyName,\n config: fallbackTarget.config,\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, fallbackTarget, createErrors.join(' | '))\n }\n createErrors.push(`${fallbackTarget.state}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${fallbackTarget.state}: ${errorText(err)}`)\n }\n return settleCreateExhaustion(options, target, createErrors)\n } catch (err) {\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, target, errorText(err))\n }\n}\n","import { randomInt } from 'node:crypto'\nimport Kernel from '@onkernel/sdk'\n\nconst BACKUP_PROXY_HOST = 'pr.oxylabs.io'\nconst BACKUP_PROXY_PORT = 7777\nconst BACKUP_STICKY_SESSION_MINUTES = 10\n\nexport const BACKUP_LAST_RESORT_ATTEMPTS = 2\n\ntype BackupProxyCreateResponse = {\n id?: string\n}\n\nexport function backupProxyUsername(): string | undefined {\n const value = process.env.OXYLABS_USERNAME?.trim()\n return value || undefined\n}\n\nexport function backupProxyPassword(): string | undefined {\n const value = process.env.OXYLABS_PASSWORD?.trim()\n return value || undefined\n}\n\nexport function backupProxyCountry(): string {\n const value = process.env.OXYLABS_COUNTRY?.trim()\n return (value || 'US').toUpperCase()\n}\n\nexport function backupProxyAvailable(): boolean {\n return Boolean(backupProxyUsername() && backupProxyPassword())\n}\n\nfunction randomStickySessionId(): string {\n let digits = ''\n for (let i = 0; i < 10; i++) digits += String(randomInt(0, 10))\n return digits\n}\n\nexport function buildBackupUsername(opts: { country?: string; sticky?: boolean } = {}): string | undefined {\n const user = backupProxyUsername()\n if (!user) return undefined\n const base = user.startsWith('customer-') ? user : `customer-${user}`\n const country = (opts.country?.trim() || backupProxyCountry()).toUpperCase()\n const sticky = opts.sticky ? `-sessid-${randomStickySessionId()}-sesstime-${BACKUP_STICKY_SESSION_MINUTES}` : ''\n return `${base}-cc-${country}${sticky}`\n}\n\nexport function scrubBackupProxyText(message: string): string {\n return message\n .replace(/pr\\.oxylabs\\.io(:\\d+)?/gi, 'backup proxy')\n .replace(/oxylabs/gi, 'backup proxy')\n}\n\nfunction backupProxyName(attemptIndex: number | undefined): string {\n const stamp = `${Date.now()}-${attemptIndex ?? 0}-${Math.random().toString(36).slice(2, 8)}`\n return `mcp-backup-custom-${stamp}`\n}\n\nexport async function createBackupProxyId(\n kernelApiKey: string,\n opts: { sticky?: boolean; attemptIndex?: number } = {},\n): Promise<string> {\n const username = buildBackupUsername({ sticky: opts.sticky })\n const password = backupProxyPassword()\n if (!username || !password) throw new Error('backup proxy pool is not configured')\n const kernel = new Kernel({ apiKey: kernelApiKey })\n try {\n const created = await kernel.proxies.create({\n type: 'custom',\n name: backupProxyName(opts.attemptIndex),\n protocol: 'http',\n config: {\n host: BACKUP_PROXY_HOST,\n port: BACKUP_PROXY_PORT,\n username,\n password,\n },\n }) as BackupProxyCreateResponse\n if (!created.id) throw new Error('backup proxy creation did not return a proxy id')\n return created.id\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(scrubBackupProxyText(message))\n }\n}\n\nexport async function createBackupProxyIdSafe(\n kernelApiKey: string | undefined,\n attemptIndex: number,\n): Promise<string | undefined> {\n if (!kernelApiKey || !backupProxyAvailable()) return undefined\n try {\n return await createBackupProxyId(kernelApiKey, { attemptIndex })\n } catch {\n return undefined\n }\n}\n\nexport async function cleanupBackupProxyId(\n kernelApiKey: string | undefined,\n proxyId: string | undefined,\n): Promise<void> {\n if (!kernelApiKey || !proxyId) return\n try {\n const kernel = new Kernel({ apiKey: kernelApiKey })\n await kernel.proxies.delete(proxyId)\n } catch {\n void 0\n }\n}\n\nexport async function withBackupProxyCleanup<T>(\n kernelApiKey: string | undefined,\n proxyId: string | undefined,\n fn: (proxyId: string | undefined) => Promise<T>,\n): Promise<T> {\n try {\n return await fn(proxyId)\n } finally {\n await cleanupBackupProxyId(kernelApiKey, proxyId)\n }\n}\n","import { DEFAULT_PROXY_MODE, HarvestOptionsSchema } from './schemas.js'\nimport { browserServiceApiKey, browserServiceProxyId } from './lib/browser-service-env.js'\nimport type { HarvestDebugSnapshot, HarvestOptions, HarvestResult, ProxyMode } from './types.js'\nimport { BrowserDriver } from './driver/BrowserDriver.js'\nimport { PAAExtractor } from './extractor/PAAExtractor.js'\nimport { OutputSerializer } from './output/OutputSerializer.js'\nimport { ProgressReporter } from './output/ProgressReporter.js'\nimport { CaptchaError, LocationMismatchError, RequestAbortedError, sanitizeVendorName } from './errors.js'\nimport type { BrowserCloseResult } from './driver/IBrowserDriver.js'\nimport { deleteKernelProxyId, resolveBackupKernelProxyId, resolveKernelProxyId } from './kernel-proxy-resolver.js'\nimport { BACKUP_LAST_RESORT_ATTEMPTS, backupProxyAvailable } from './backup-proxy.js'\nimport { currentCostContext, runWithCostContext } from './api/cost-context.js'\n\nconst MAX_ATTEMPTS = 3\nconst LOCATION_PROXY_MAX_ATTEMPTS = 5\n\nexport type HarvestAttemptOutcome =\n | 'paa_found'\n | 'paa_partial'\n | 'no_paa'\n | 'serp_only'\n | 'degraded_result'\n | 'captcha'\n | 'location_mismatch'\n | 'proxy_tunnel_failed'\n | 'proxy_unavailable'\n | 'request_aborted'\n | 'timeout'\n | 'error'\n\nexport type HarvestAttemptLogEvent =\n | {\n type: 'started'\n attemptNumber: number\n maxAttempts: number\n query: string\n location: string | null\n maxQuestions: number\n startedAt: string\n }\n | {\n type: 'finished'\n attemptNumber: number\n maxAttempts: number\n outcome: HarvestAttemptOutcome\n kernelSessionId: string | null\n questionCount: number\n durationMs: number\n error: string | null\n willRetry: boolean\n cleanup: BrowserCloseResult\n debug: HarvestDebugSnapshot | null\n completedAt: string\n }\n\ntype HarvestAttemptLogSink = (event: HarvestAttemptLogEvent) => void | Promise<void>\n\ntype ExtractOnceOutcome =\n | { result: HarvestResult; error: null; cleanup: BrowserCloseResult; debug: HarvestDebugSnapshot | null; salvagedFrom?: unknown }\n | { result: null; error: unknown; cleanup: BrowserCloseResult; debug: HarvestDebugSnapshot | null; salvagedFrom?: undefined }\n\nfunction abortReason(signal: AbortSignal): unknown {\n if (signal.reason instanceof DOMException && signal.reason.name === 'TimeoutError') return signal.reason\n return new RequestAbortedError()\n}\n\nfunction getAbortSignal(rawOptions: unknown): AbortSignal | undefined {\n if (!rawOptions || typeof rawOptions !== 'object') return undefined\n const signal = (rawOptions as { signal?: unknown }).signal\n if (signal instanceof AbortSignal) return signal\n return undefined\n}\n\nfunction getAttemptLogSink(rawOptions: unknown): HarvestAttemptLogSink | undefined {\n if (!rawOptions || typeof rawOptions !== 'object') return undefined\n const sink = (rawOptions as { onAttemptEvent?: unknown }).onAttemptEvent\n return typeof sink === 'function' ? sink as HarvestAttemptLogSink : undefined\n}\n\nasync function emitAttemptEvent(sink: HarvestAttemptLogSink | undefined, event: HarvestAttemptLogEvent): Promise<void> {\n if (!sink) return\n try {\n await sink(event)\n } catch (err) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_log_failed',\n attempt_number: event.attemptNumber,\n message: err instanceof Error ? err.message : String(err),\n }))\n }\n}\n\nfunction classifyAttemptError(err: unknown): HarvestAttemptOutcome {\n if (err instanceof CaptchaError) return 'captcha'\n if (err instanceof LocationMismatchError) return 'location_mismatch'\n if (err instanceof RequestAbortedError) return 'request_aborted'\n if (err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError')) return 'timeout'\n const message = err instanceof Error ? err.message : String(err)\n if (looksLikeProxyTunnelFailure(message)) return 'proxy_tunnel_failed'\n if (looksLikeProxyUnavailable(message)) return 'proxy_unavailable'\n return /timeout|timed out|Timeout \\d+ms exceeded|deadline/i.test(message) ? 'timeout' : 'error'\n}\n\nfunction classifyAttemptResult(result: HarvestResult): HarvestAttemptOutcome {\n if (result.diagnostics.degradedResult) return 'degraded_result'\n return result.diagnostics?.completionStatus ?? (result.totalQuestions > 0 ? 'paa_found' : 'no_paa')\n}\n\nfunction resultHasUsableContent(result: HarvestResult): boolean {\n return result.flat.length > 0 ||\n result.organicResults.length > 0 ||\n result.localPack.length > 0 ||\n result.videos.length > 0 ||\n result.forums.length > 0 ||\n result.whatPeopleSaying.length > 0 ||\n result.aiOverview.detected ||\n result.aiMode.detected\n}\n\nfunction withResultQuality(result: HarvestResult): HarvestResult {\n const hasUsableContent = resultHasUsableContent(result)\n const hasWarnings = (result.diagnostics.warnings?.length ?? 0) > 0\n const resultQuality = hasUsableContent\n ? (hasWarnings ? 'partial' : 'complete')\n : 'degraded'\n const degradedResult = resultQuality === 'degraded'\n\n result.diagnostics = {\n ...result.diagnostics,\n resultQuality,\n degradedResult,\n degradationReasons: degradedResult ? ['empty_primary_serp'] : [],\n retryRecommended: degradedResult,\n }\n return result\n}\n\nfunction degradedResultMessage(result: HarvestResult): string {\n const reasons = result.diagnostics.degradationReasons ?? []\n if (reasons.includes('empty_primary_serp')) {\n return 'No usable primary SERP surface was captured; the result may be incomplete.'\n }\n return 'The harvest result was marked degraded.'\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction maxAttemptsForProxyMode(proxyMode: ProxyMode): number {\n return proxyMode === 'location' ? LOCATION_PROXY_MAX_ATTEMPTS : MAX_ATTEMPTS\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\nfunction retryableLocationProxyError(outcome: HarvestAttemptOutcome): boolean {\n return outcome === 'captcha' || outcome === 'proxy_tunnel_failed' || outcome === 'proxy_unavailable'\n}\n\nfunction locationMismatchMessage(result: HarvestResult): string {\n const evidence = result.diagnostics.debug?.locationEvidence\n const expected = evidence?.expected?.canonicalLocation ?? result.location ?? 'requested location'\n const candidates = evidence?.candidates\n .slice(0, 3)\n .map(candidate => `${candidate.city}, ${candidate.regionCode}`)\n .join('; ')\n return candidates\n ? `Google returned results for ${candidates}, not ${expected}`\n : `Google returned results for a different location than ${expected}`\n}\n\nfunction shouldRetryLocationMismatch(result: HarvestResult, proxyMode: ProxyMode): boolean {\n return proxyMode === 'location' && result.diagnostics.debug?.locationEvidence?.status === 'mismatch'\n}\n\nfunction stripInternalDebug(result: HarvestResult, keepDebug: boolean): HarvestResult {\n if (keepDebug || !result.diagnostics.debug) return result\n const diagnostics = { ...result.diagnostics }\n delete diagnostics.debug\n return { ...result, diagnostics }\n}\n\nasync function cleanupDisposableProxy(kernelApiKey: string | undefined, proxyId: string | undefined): Promise<void> {\n if (!kernelApiKey || !proxyId) return\n try {\n await deleteKernelProxyId(kernelApiKey, proxyId)\n console.info(JSON.stringify({\n event: 'kernel_proxy_deleted',\n proxy_id_suffix: proxyId.slice(-6),\n }))\n } catch (err) {\n console.warn(JSON.stringify({\n event: 'kernel_proxy_delete_failed',\n proxy_id_suffix: proxyId.slice(-6),\n message: errorMessage(err),\n }))\n }\n}\n\nasync function extractOnce(options: HarvestOptions, signal?: AbortSignal): Promise<ExtractOnceOutcome> {\n const driver = new BrowserDriver()\n const reporter = new ProgressReporter()\n const extractor = new PAAExtractor(driver, reporter)\n if (signal?.aborted) {\n return {\n result: null,\n error: abortReason(signal),\n cleanup: await driver.close(),\n debug: null,\n }\n }\n\n let onAbort: (() => void) | undefined\n const abortPromise = signal\n ? new Promise<never>((_, reject) => {\n onAbort = () => reject(abortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n })\n : null\n\n let result: HarvestResult | null = null\n let error: unknown = null\n let cleanup: BrowserCloseResult\n let debug: HarvestDebugSnapshot | null = null\n try {\n const extraction = extractor.extract(options, signal)\n if (abortPromise) extraction.catch(() => {})\n result = await (abortPromise ? Promise.race([extraction, abortPromise]) : extraction)\n } catch (err) {\n error = err\n } finally {\n if (signal && onAbort) signal.removeEventListener('abort', onAbort)\n debug = result?.diagnostics.debug ?? (options.debug ? {\n enabled: true,\n request: {\n query: options.query,\n locationInput: options.location ?? null,\n canonicalLocation: null,\n uule: null,\n gl: options.gl,\n hl: options.hl,\n device: options.device,\n proxyMode: options.proxyMode,\n proxyZip: options.proxyZip ?? null,\n serpOnly: options.serpOnly,\n pages: options.pages ?? 1,\n },\n browser: driver.getDebugSnapshot(),\n } : null)\n cleanup = await driver.close()\n }\n if (error) {\n const outcome = classifyAttemptError(error)\n const budgetExhausted = outcome === 'timeout' || outcome === 'request_aborted'\n let salvaged: HarvestResult | null = null\n if (budgetExhausted) {\n try {\n salvaged = typeof extractor.getPartialResult === 'function' ? extractor.getPartialResult() : null\n } catch {\n salvaged = null\n }\n }\n if (salvaged) {\n return { result: salvaged, error: null, cleanup: cleanup!, debug, salvagedFrom: error }\n }\n return { result: null, error, cleanup: cleanup!, debug }\n }\n return { result: result!, error: null, cleanup: cleanup!, debug }\n}\n\nexport async function harvest(rawOptions: unknown): Promise<HarvestResult> {\n const raw = typeof rawOptions === 'object' && rawOptions !== null ? rawOptions : {}\n const signal = getAbortSignal(rawOptions)\n const onAttemptEvent = getAttemptLogSink(rawOptions)\n const requestedProxyMode = (raw as { proxyMode?: unknown }).proxyMode\n const proxyMode = requestedProxyMode === 'location' || requestedProxyMode === 'none' || requestedProxyMode === 'configured'\n ? requestedProxyMode\n : DEFAULT_PROXY_MODE\n const kernelApiKey = typeof (raw as { kernelApiKey?: unknown }).kernelApiKey === 'string'\n ? ((raw as { kernelApiKey: string }).kernelApiKey).trim()\n : browserServiceApiKey()\n const configuredKernelProxyId = typeof (raw as { kernelProxyId?: unknown }).kernelProxyId === 'string'\n ? ((raw as { kernelProxyId: string }).kernelProxyId).trim()\n : browserServiceProxyId()\n const proxyOpts = {\n kernelApiKey,\n proxyMode: proxyMode as ProxyMode,\n configuredKernelProxyId,\n location: typeof (raw as { location?: unknown }).location === 'string' ? (raw as { location: string }).location : undefined,\n proxyZip: typeof (raw as { proxyZip?: unknown }).proxyZip === 'string' ? (raw as { proxyZip: string }).proxyZip : undefined,\n gl: typeof (raw as { gl?: unknown }).gl === 'string' ? (raw as { gl: string }).gl : 'us',\n }\n const requestedDebug = typeof (raw as { debug?: unknown }).debug === 'boolean'\n ? (raw as { debug: boolean }).debug\n : false\n const needsLocationEvidence = proxyMode === 'location' && Boolean(proxyOpts.location)\n const requestedMaxAttempts = (raw as { maxAttempts?: unknown }).maxAttempts\n const baseMaxAttempts = typeof requestedMaxAttempts === 'number' && Number.isInteger(requestedMaxAttempts) && requestedMaxAttempts >= 1\n ? requestedMaxAttempts\n : maxAttemptsForProxyMode(proxyMode as ProxyMode) + 1\n const maxAttempts = baseMaxAttempts + (backupProxyAvailable() ? BACKUP_LAST_RESORT_ATTEMPTS : 0)\n const softDeadlineMs = typeof (raw as { softDeadlineMs?: unknown }).softDeadlineMs === 'number'\n ? (raw as { softDeadlineMs: number }).softDeadlineMs\n : null\n const serializer = new OutputSerializer()\n let lastError: unknown = null\n let effectiveProxyMode: ProxyMode = proxyMode as ProxyMode\n let proxyFailures = 0\n\n for (let i = 0; i < maxAttempts; i++) {\n const attemptNumber = i + 1\n const startedAtMs = Date.now()\n try {\n if (signal?.aborted) throw abortReason(signal)\n const isBackupAttempt = i >= baseMaxAttempts\n if (isBackupAttempt && softDeadlineMs !== null && Date.now() >= softDeadlineMs) break\n const resolution = isBackupAttempt\n ? await resolveBackupKernelProxyId({ kernelApiKey, proxyMode: effectiveProxyMode, attemptIndex: i })\n : await resolveKernelProxyId({\n ...proxyOpts,\n proxyMode: effectiveProxyMode,\n attemptIndex: i,\n fresh: effectiveProxyMode === 'location',\n backupOnExhaustion: true,\n })\n if (!resolution) break\n const mergedAttempt = {\n ...raw,\n kernelApiKey,\n kernelProxyId: resolution.kernelProxyId,\n kernelProxyResolution: resolution.resolution,\n proxyMode: effectiveProxyMode,\n debug: requestedDebug || needsLocationEvidence,\n }\n if (effectiveProxyMode === 'none' && !isBackupAttempt) (mergedAttempt as Record<string, unknown>).kernelProxyId = undefined\n const attemptOptions = HarvestOptionsSchema.parse(mergedAttempt)\n await emitAttemptEvent(onAttemptEvent, {\n type: 'started',\n attemptNumber,\n maxAttempts,\n query: attemptOptions.query,\n location: attemptOptions.location ?? null,\n maxQuestions: attemptOptions.maxQuestions,\n startedAt: new Date(startedAtMs).toISOString(),\n })\n console.info(JSON.stringify({\n event: 'harvest_attempt_started',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n query: attemptOptions.query,\n location: attemptOptions.location ?? null,\n max_questions: attemptOptions.maxQuestions,\n }))\n const baseCtx = currentCostContext()\n const attempt = baseCtx\n ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal))\n : await extractOnce(attemptOptions, signal)\n if (attempt.error) {\n const err = attempt.error\n const outcome = classifyAttemptError(err)\n if ((outcome === 'captcha' || outcome === 'proxy_tunnel_failed') && effectiveProxyMode === 'location') {\n proxyFailures++\n if (proxyFailures >= 2) effectiveProxyMode = 'none'\n }\n const willRetry = i < maxAttempts - 1 && (\n outcome === 'captcha' || (proxyMode === 'location' && retryableLocationProxyError(outcome))\n )\n if (outcome === 'captcha') {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_captcha',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n } else if (willRetry) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_proxy_retry',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n outcome,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n }\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome,\n kernelSessionId: attempt.cleanup.kernelSessionId,\n questionCount: 0,\n durationMs: Date.now() - startedAtMs,\n error: errorMessage(err),\n willRetry,\n cleanup: attempt.cleanup,\n debug: attempt.debug,\n completedAt: new Date().toISOString(),\n })\n await cleanupDisposableProxy(kernelApiKey, resolution.disposableProxyId)\n lastError = err\n if (willRetry) continue\n break\n }\n const result = attempt.result\n if (!result) throw new Error('Harvest attempt completed without a result')\n if (shouldRetryLocationMismatch(result, proxyMode as ProxyMode)) {\n const err = new LocationMismatchError(locationMismatchMessage(result))\n const willRetry = i < maxAttempts - 1\n console.warn(JSON.stringify({\n event: 'harvest_attempt_location_mismatch',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n message: err.message,\n will_retry: willRetry,\n }))\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome: 'location_mismatch',\n kernelSessionId: attempt.cleanup.kernelSessionId,\n questionCount: result.totalQuestions,\n durationMs: Date.now() - startedAtMs,\n error: err.message,\n willRetry,\n cleanup: attempt.cleanup,\n debug: attempt.debug,\n completedAt: new Date().toISOString(),\n })\n await cleanupDisposableProxy(kernelApiKey, resolution.disposableProxyId)\n lastError = err\n if (willRetry) continue\n break\n }\n const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug))\n const outcome = classifyAttemptResult(finalResult)\n const willRetry = outcome === 'degraded_result' && i < maxAttempts - 1\n const resultError = outcome === 'degraded_result' ? degradedResultMessage(finalResult) : null\n if (willRetry) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_degraded_result',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n degradation_reasons: finalResult.diagnostics.degradationReasons,\n will_retry: true,\n }))\n }\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome,\n kernelSessionId: attempt.cleanup.kernelSessionId,\n questionCount: finalResult.totalQuestions,\n durationMs: Date.now() - startedAtMs,\n error: resultError,\n willRetry,\n cleanup: attempt.cleanup,\n debug: attempt.debug,\n completedAt: new Date().toISOString(),\n })\n await cleanupDisposableProxy(kernelApiKey, resolution.disposableProxyId)\n if (willRetry) continue\n if (attemptOptions.format === 'json' || attemptOptions.format === 'both') {\n await serializer.writeJSON(finalResult, attemptOptions.outputDir)\n }\n if (attemptOptions.format === 'csv' || attemptOptions.format === 'both') {\n await Promise.all([\n serializer.writeCSV(finalResult.flat, attemptOptions.outputDir),\n finalResult.videos.length > 0 ? serializer.writeVideoCSV(finalResult.videos, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.forums.length > 0 ? serializer.writeForumCSV(finalResult.forums, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.aiOverview.detected ? serializer.writeAIOverviewCSV(finalResult.aiOverview.citations, finalResult.aiOverview.text, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.aiMode.detected ? serializer.writeAIModeCSV(finalResult.aiMode.citations, finalResult.aiMode.text, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.whatPeopleSaying.length > 0 ? serializer.writeWhatPeopleSayingCSV(finalResult.whatPeopleSaying, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n ])\n }\n return finalResult\n } catch (err) {\n const outcome = classifyAttemptError(err)\n if ((outcome === 'captcha' || outcome === 'proxy_tunnel_failed') && effectiveProxyMode === 'location') {\n proxyFailures++\n if (proxyFailures >= 2) effectiveProxyMode = 'none'\n }\n const willRetry = i < maxAttempts - 1 && (\n outcome === 'captcha' || (proxyMode === 'location' && retryableLocationProxyError(outcome))\n )\n if (outcome === 'captcha') {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_captcha',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n } else if (willRetry) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_proxy_retry',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n outcome,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n }\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome,\n kernelSessionId: null,\n questionCount: 0,\n durationMs: Date.now() - startedAtMs,\n error: errorMessage(err),\n willRetry,\n cleanup: {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: null,\n browserCloseError: null,\n },\n debug: null,\n completedAt: new Date().toISOString(),\n })\n lastError = err\n if (willRetry) continue\n if (outcome === 'captcha') break\n throw err\n }\n }\n if (lastError && !(lastError instanceof CaptchaError)) throw lastError\n console.warn(JSON.stringify({\n event: 'harvest_captcha_exhausted',\n max_attempts: maxAttempts,\n session_kind: kernelApiKey ? 'kernel' : 'local',\n }))\n throw new CaptchaError(sanitizeVendorName(`CAPTCHA on all ${maxAttempts} fresh sessions. Try again in a few minutes.`))\n}\n","#!/usr/bin/env node\nimport { runCli } from '../src/cli.js'\nrunCli()\n"],"mappings":"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","names":["import_node_crypto","import_zod","StealthPlugin","Kernel","playwrightChromium","DESKTOP_USER_AGENT","MOBILE_USER_AGENT","s","organicResults","localPack","rawEntityIds","entityIds","aiSurfaces","locationEvidence","allOrganic","stats","fs","path","Papa","import_sdk","import_node_crypto","import_sdk","Kernel","proxyIdSuffix","errorText","Kernel","Kernel","createErrors","errorText","resolution","outcome","willRetry"]}
1
+ {"version":3,"sources":["../../src/cli.ts","../../src/lib/browser-service-env.ts","../../src/schemas.ts","../../src/driver/BrowserDriver.ts","../../src/selectors.ts","../../src/errors.ts","../../src/api/cost-telemetry.ts","../../src/api/db.ts","../../src/api/cost-context.ts","../../src/api/cost-rates.ts","../../src/locations.ts","../../src/uule.ts","../../src/serp-location-query.ts","../../src/serp-location-debug.ts","../../src/lib/paa-answer-cleanup.ts","../../src/extractor/ai-surfaces.ts","../../src/extractor/PAAExtractor.ts","../../src/output/OutputSerializer.ts","../../src/output/memory-library-sink.ts","../../src/output/ProgressReporter.ts","../../src/kernel-proxy-resolver.ts","../../src/backup-proxy.ts","../../src/harvest.ts","../../bin/paa-harvest.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { browserServiceApiKey } from './lib/browser-service-env.js'\nimport { harvest } from './harvest.js'\n\nconst program = new Command()\n\nprogram\n .name('paa-harvest')\n .description('Recursively extract Google People Also Ask questions')\n .requiredOption('-q, --query <query>', 'Seed query')\n .option('-l, --location <location>', 'Location name (e.g. \"austin\" or \"Austin,Texas,United States\")')\n .option('--gl <gl>', 'Google country code', 'us')\n .option('--hl <hl>', 'Google language code', 'en')\n .option('-d, --depth <depth>', 'BFS depth (1-30)', '3')\n .option('-m, --max-questions <n>', 'Max questions to harvest', '100')\n .option('-o, --output <dir>', 'Output directory', './paa-output')\n .option('-f, --format <format>', 'Output format: json, csv, or both', 'both')\n .option('--headless', 'Run browser in headless mode', false)\n .option('--profile <dir>', 'Persistent browser profile directory')\n .option('--proxy <url>', 'Proxy server URL')\n .option('--kernel-api-key <key>', 'Browser service API key (or set BROWSER_SERVICE_API_KEY env var)')\n .action(async (opts) => {\n try {\n const result = await harvest({\n query: opts.query,\n location: opts.location,\n gl: opts.gl,\n hl: opts.hl,\n depth: parseInt(opts.depth, 10),\n maxQuestions: parseInt(opts.maxQuestions, 10),\n outputDir: opts.output,\n format: opts.format,\n headless: opts.headless,\n profileDir: opts.profile,\n proxy: opts.proxy,\n kernelApiKey: opts.kernelApiKey ?? browserServiceApiKey(),\n })\n console.log(JSON.stringify({ totalQuestions: result.totalQuestions, outputDir: result.stats.seed }))\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n })\n\nexport async function runCli(): Promise<void> {\n await program.parseAsync()\n}\n","export function browserServiceApiKey(): string | undefined {\n const value = (process.env.BROWSER_SERVICE_API_KEY ?? process.env.KERNEL_API_KEY)?.trim()\n return value || undefined\n}\n\nexport function browserServiceProxyId(): string | undefined {\n return undefined\n}\n\nexport function browserServiceProfileName(): string | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_NAME ??\n process.env.BROWSER_SERVICE_PROFILE_NAME ??\n process.env.KERNEL_BROWSER_PROFILE_NAME ??\n process.env.KERNEL_PROFILE_NAME\n )?.trim()\n return value || undefined\n}\n\nexport function browserServiceProfileSaveChanges(): boolean | undefined {\n const value = (\n process.env.BROWSER_AGENT_PROFILE_SAVE_CHANGES ??\n process.env.BROWSER_SERVICE_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_BROWSER_PROFILE_SAVE_CHANGES ??\n process.env.KERNEL_PROFILE_SAVE_CHANGES\n )?.trim().toLowerCase()\n if (!value) return undefined\n if (['1', 'true', 'yes', 'on'].includes(value)) return true\n if (['0', 'false', 'no', 'off'].includes(value)) return false\n return undefined\n}\n","import { z } from 'zod'\n\nexport const DEFAULT_PROXY_MODE = 'none' as const\nexport const DEFAULT_MAPS_PROXY_MODE = 'none' as const\n\nexport const HarvestOptionsSchema = z.object({\n query: z.string().min(1),\n location: z.string().optional(),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n device: z.enum(['desktop', 'mobile']).default('desktop'),\n proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).optional(),\n keepDefaultProxy: z.boolean().optional(),\n requireUsEgress: z.boolean().optional(),\n maxAttempts: z.number().int().min(1).max(12).optional(),\n debug: z.boolean().default(false),\n depth: z.number().int().min(1).max(30).default(3),\n maxQuestions: z.number().int().min(1).max(1000).default(100),\n headless: z.boolean().default(false),\n profileDir: z.string().optional(),\n proxy: z.string().url().optional(),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n 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 recency: z.enum(['day', 'week', 'month', 'year']).optional(),\n softDeadlineMs: z.number().optional(),\n})\n\nexport function recencyToTbs(recency: 'day' | 'week' | 'month' | 'year' | undefined): string | undefined {\n switch (recency) {\n case 'day': return 'qdr:d'\n case 'week': return 'qdr:w'\n case 'month': return 'qdr:m'\n case 'year': return 'qdr:y'\n default: return undefined\n }\n}\n\nexport const MapsPlaceOptionsSchema = z.object({\n businessName: z.string().min(1),\n location: z.string().min(1),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n includeReviews: z.boolean().default(false),\n maxReviews: z.number().int().min(1).max(500).default(50),\n includeServices: z.boolean().default(false),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n headless: z.boolean().default(true),\n})\n\nexport const MapsSearchOptionsSchema = z.object({\n query: z.string().min(1),\n location: z.string().optional(),\n gl: z.string().length(2).default('us'),\n hl: z.string().length(2).default('en'),\n maxResults: z.number().int().min(1).max(50).default(10),\n includeServices: z.boolean().default(false),\n proxyMode: z.enum(['location', 'configured', 'none']).default(DEFAULT_MAPS_PROXY_MODE),\n proxyZip: z.string().regex(/^\\d{5}$/).optional(),\n serpRedirect: z.boolean().optional(),\n forceDirectEgress: z.boolean().optional(),\n debug: z.boolean().default(false),\n kernelApiKey: z.string().optional(),\n kernelProxyId: z.string().optional(),\n kernelProxyResolution: z.unknown().optional(),\n headless: z.boolean().default(true),\n})\n\nexport const RawPAAItemSchema = z.object({\n question: z.string().min(1),\n answer: z.string().optional(),\n sourceTitle: z.string().optional(),\n sourceSite: z.string().optional(),\n sourceCite: z.string().optional(),\n})\n\nexport const RawMapsOverviewSchema = z.object({\n name: z.string().nullable(),\n rating: z.string().nullable(),\n reviewCount: z.string().nullable(),\n category: z.string().nullable(),\n address: z.string().nullable(),\n hoursSummary: z.string().nullable(),\n phone: z.string().nullable(),\n phoneDisplay: z.string().nullable(),\n website: z.string().nullable(),\n plusCode: z.string().nullable(),\n bookingUrl: z.string().nullable(),\n})\n\nexport const RawMapsHoursRowSchema = z.object({\n day: z.string(),\n hours: z.string(),\n})\n\nexport const RawMapsReviewStatsSchema = z.object({\n reviewHistogram: z.array(z.object({\n stars: z.number(),\n count: z.string(),\n })),\n reviewTopics: z.array(z.object({\n label: z.string(),\n count: z.string(),\n })),\n})\n\nexport const RawMapsReviewCardSchema = z.object({\n reviewId: z.string(),\n author: z.string().nullable(),\n stars: z.string().nullable(),\n date: z.string().nullable(),\n text: z.string().nullable(),\n ownerResponse: z.string().nullable(),\n})\n\nexport const RawMapsAboutAttributeSchema = z.object({\n section: z.string(),\n attribute: z.string(),\n})\n","import { chromium } from 'playwright-extra'\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth'\nimport { chromium as playwrightChromium } from 'playwright'\nimport type { Browser, BrowserContext, Page } from 'playwright'\nimport Kernel from '@onkernel/sdk'\nimport { PAASelectors } from '../selectors.js'\nimport { CaptchaError, ExtractionError, RECAPTCHA_INSTRUCTIONS } from '../errors.js'\nimport type { BrowserDebugSnapshot, BrowserNetworkLocationDebug, DriverConfig, KernelSessionDebug, ProxyMode, SerpDevice, SerpNavigationDebug, UuleString } from '../types.js'\nimport type { BrowserCloseResult, IBrowserDriver } from './IBrowserDriver.js'\nimport { recordKernelSession } from '../api/cost-telemetry.js'\nimport { currentCostContext } from '../api/cost-context.js'\nimport { HEADLESS_OPS } from '../api/cost-rates.js'\n\nchromium.use(StealthPlugin())\n\nconst DESKTOP_USER_AGENT =\n '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'\n\nconst MOBILE_USER_AGENT =\n 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1'\n\nconst DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS = 180\nconst KERNEL_BROWSER_CLOSE_TIMEOUT_MS = 3_000\nconst KERNEL_SESSION_DELETE_TIMEOUT_MS = 5_000\n\nfunction positiveIntFromEnv(name: string, fallback: number): number {\n const raw = process.env[name]\n if (!raw) return fallback\n const parsed = Number(raw)\n return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback\n}\n\nfunction proxyIdSuffix(proxyId: string | null | undefined): string | null {\n return proxyId ? proxyId.slice(-6) : null\n}\n\nexport function serpArrivalUrl(googleUrl: string, baseOverride?: string): string {\n const base = (baseOverride ?? process.env.SERP_REDIRECT_BASE)?.trim().replace(/\\/+$/, '')\n if (!base) return googleUrl\n return `${base}/g/${Buffer.from(googleUrl, 'utf8').toString('base64url')}`\n}\n\nexport function isUsCountry(country: string | null | undefined): boolean {\n if (!country) return false\n return /united states|u\\.?s\\.?a?\\.?|^us$/i.test(country.trim())\n}\n\nfunction errorText(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction isKernelProfileConflict(err: unknown): boolean {\n return /\\b409\\b|conflict|already exists/i.test(errorText(err))\n}\n\nasync function ensureKernelProfile(kernelClient: Kernel, name: string): Promise<void> {\n try {\n await kernelClient.profiles.create({ name })\n } catch (err) {\n if (!isKernelProfileConflict(err)) throw err\n }\n}\n\nexport function rankCheckContextOptions(config: DriverConfig) {\n return {\n viewport: config.viewport,\n locale: config.locale,\n userAgent: config.userAgent ?? (config.isMobile ? MOBILE_USER_AGENT : DESKTOP_USER_AGENT),\n ...(config.deviceScaleFactor ? { deviceScaleFactor: config.deviceScaleFactor } : {}),\n ...(config.isMobile !== undefined ? { isMobile: config.isMobile } : {}),\n ...(config.hasTouch !== undefined ? { hasTouch: config.hasTouch } : {}),\n }\n}\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n let timeout: ReturnType<typeof setTimeout> | undefined\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timeout = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs)\n }),\n ])\n } finally {\n if (timeout) clearTimeout(timeout)\n }\n}\n\nexport function buildYouTubeChannelVideosUrl(channelInput: string): string {\n const raw = channelInput.trim()\n if (!raw) throw new Error('channelHandle is required')\n\n const urlLike = /^https?:\\/\\//i.test(raw) || /^(www\\.|m\\.)?youtube\\.com\\//i.test(raw)\n if (urlLike) {\n const parsed = new URL(/^https?:\\/\\//i.test(raw) ? raw : `https://${raw}`)\n const host = parsed.hostname.replace(/^www\\./, '').replace(/^m\\./, '').toLowerCase()\n if (host !== 'youtube.com') throw new Error('channel URL must be on youtube.com')\n\n const segments = parsed.pathname.split('/').filter(Boolean)\n const first = segments[0] ?? ''\n const second = segments[1] ?? ''\n\n if (first.startsWith('@')) return `https://www.youtube.com/${first}/videos`\n if (first === 'channel' && second) return `https://www.youtube.com/channel/${second}/videos`\n if ((first === 'c' || first === 'user') && second) return `https://www.youtube.com/${first}/${second}/videos`\n\n throw new Error('channel URL must be a YouTube handle, /channel/UC..., /c/..., or /user/... URL')\n }\n\n const stripped = raw.replace(/^\\/+/, '').replace(/\\/+$/, '')\n const withoutVideos = stripped.replace(/\\/videos$/i, '')\n if (/^UC[\\w-]{20,}$/.test(withoutVideos)) {\n return `https://www.youtube.com/channel/${withoutVideos}/videos`\n }\n\n const handle = withoutVideos.startsWith('@') ? withoutVideos : `@${withoutVideos}`\n if (!/^@[\\w.-]+$/.test(handle)) {\n throw new Error('channelHandle must be an @handle, UC channel ID, or YouTube channel URL')\n }\n return `https://www.youtube.com/${handle}/videos`\n}\n\nexport class BrowserDriver implements IBrowserDriver {\n private browser: Browser | null = null\n private context: BrowserContext | null = null\n private page: Page | null = null\n private kernelClient: Kernel | null = null\n private kernelSessionId: string | null = null\n private kernelOpenedAtMs: number | null = null\n private kernelStealth: boolean | null = null\n private kernelProxyUsed: boolean | null = null\n private kernelProxySource: string | null = null\n private kernelProxyType: string | null = null\n private kernelHeadlessSent: boolean | null = null\n private debugEnabled = false\n private debugSnapshot: BrowserDebugSnapshot = {\n kernel: null,\n context: null,\n networkLocation: null,\n serpNavigation: null,\n }\n\n async launch(config: DriverConfig): Promise<void> {\n this.debugEnabled = config.debug === true\n const proxyMode: ProxyMode = config.proxyMode ?? (config.kernelProxyId ? 'configured' : 'none')\n const device: SerpDevice = config.isMobile ? 'mobile' : 'desktop'\n this.debugSnapshot = {\n kernel: null,\n context: {\n viewport: config.viewport,\n locale: config.locale,\n device,\n userAgent: config.userAgent ?? (config.isMobile ? MOBILE_USER_AGENT : DESKTOP_USER_AGENT),\n deviceScaleFactor: config.deviceScaleFactor ?? null,\n isMobile: config.isMobile === true,\n hasTouch: config.hasTouch === true,\n },\n networkLocation: null,\n serpNavigation: null,\n }\n\n if (config.kernelApiKey) {\n this.kernelClient = new Kernel({ apiKey: config.kernelApiKey })\n const timeoutSeconds = positiveIntFromEnv('KERNEL_BROWSER_TIMEOUT_SECONDS', DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS)\n if (config.kernelProfileName && config.kernelProfileSaveChanges === true) {\n await ensureKernelProfile(this.kernelClient, config.kernelProfileName)\n }\n const costCtx = currentCostContext()\n let useHeadless: boolean\n if (config.headlessMode === 'headful' || costCtx?.forceHeadful === true) useHeadless = false\n else if (config.headlessMode === 'headless') useHeadless = true\n else useHeadless = costCtx?.forceHeadless === true\n || (process.env.KERNEL_HEADLESS_DISABLED !== 'true' && !!costCtx?.op && HEADLESS_OPS.has(costCtx.op))\n this.kernelHeadlessSent = useHeadless ? true : null\n if (costCtx?.headlessSentOut) costCtx.headlessSentOut.value = this.kernelHeadlessSent\n const kernelBrowser = await this.kernelClient.browsers.create({\n stealth: true,\n timeout_seconds: timeoutSeconds,\n ...(useHeadless ? { headless: true } : {}),\n ...(config.kernelProxyId ? { proxy_id: config.kernelProxyId } : {}),\n ...(config.kernelProfileName\n ? {\n profile: {\n name: config.kernelProfileName,\n ...(typeof config.kernelProfileSaveChanges === 'boolean' ? { save_changes: config.kernelProfileSaveChanges } : {}),\n },\n }\n : {}),\n })\n this.kernelSessionId = kernelBrowser.session_id\n this.kernelOpenedAtMs = Date.now()\n this.kernelStealth = typeof kernelBrowser.stealth === 'boolean' ? kernelBrowser.stealth : null\n this.kernelProxyUsed = typeof kernelBrowser.proxy_id === 'string' ? Boolean(kernelBrowser.proxy_id) : Boolean(config.kernelProxyId)\n this.kernelProxySource = config.kernelProxyResolution?.source ?? null\n this.kernelProxyType = config.kernelProxyResolution?.proxyType ?? null\n let defaultProxyDisabled: boolean | null = null\n let defaultProxyDisableError: string | null = null\n if (!config.kernelProxyId && !config.keepDefaultProxy) {\n try {\n await withTimeout(\n this.kernelClient.browsers.update(this.kernelSessionId, { disable_default_proxy: true }),\n 5_000,\n `Kernel session ${this.kernelSessionId} disable default proxy`,\n )\n defaultProxyDisabled = true\n } catch (err) {\n defaultProxyDisabled = false\n defaultProxyDisableError = errorText(err)\n }\n }\n const kernelDebug: KernelSessionDebug = {\n sessionId: this.kernelSessionId,\n proxyMode,\n requestedProxyIdPresent: Boolean(config.kernelProxyId),\n requestedProxyIdSuffix: proxyIdSuffix(config.kernelProxyId),\n createdProxyIdPresent: typeof kernelBrowser.proxy_id === 'string' ? Boolean(kernelBrowser.proxy_id) : null,\n createdProxyIdSuffix: proxyIdSuffix(kernelBrowser.proxy_id),\n retrievedProxyIdPresent: null,\n retrievedProxyIdSuffix: null,\n retrievedProxyIdMatchesRequested: null,\n defaultProxyDisabled,\n defaultProxyDisableError,\n proxyResolution: config.kernelProxyResolution ?? null,\n timeoutSeconds,\n stealth: typeof kernelBrowser.stealth === 'boolean' ? kernelBrowser.stealth : null,\n profilePresent: null,\n poolPresent: null,\n retrieveError: null,\n }\n this.debugSnapshot.kernel = kernelDebug\n console.info(JSON.stringify({\n event: 'kernel_browser_created',\n kernel_session_id: this.kernelSessionId,\n timeout_seconds: timeoutSeconds,\n proxy_mode: proxyMode,\n proxy_id_present: Boolean(config.kernelProxyId),\n profile_name_present: Boolean(config.kernelProfileName),\n proxy_resolution_source: config.kernelProxyResolution?.source,\n }))\n if (this.debugEnabled) {\n await this.populateKernelRetrieveDebug(kernelDebug, config.kernelProxyId)\n }\n this.browser = await playwrightChromium.connectOverCDP(kernelBrowser.cdp_ws_url)\n this.context = await this.browser.newContext(rankCheckContextOptions(config))\n await this.installEsbuildHelperShims(this.context)\n this.page = await this.context.newPage()\n await this.page.setViewportSize(config.viewport)\n if (this.debugEnabled || config.requireUsEgress) {\n this.debugSnapshot.networkLocation = await this.captureBrowserNetworkLocation()\n }\n if (config.requireUsEgress) {\n const country = this.debugSnapshot.networkLocation?.country ?? null\n if (!isUsCountry(country)) {\n throw new CaptchaError(`Non-US egress (${country ?? 'unknown'}) — retrying with a fresh session`)\n }\n }\n return\n }\n\n const launchOpts = {\n headless: config.headless,\n proxy: config.proxy ? { server: config.proxy } : undefined,\n ...(config.browserExecutablePath ? { executablePath: config.browserExecutablePath } : {}),\n ...(config.ignoreDefaultArgs ? { ignoreDefaultArgs: config.ignoreDefaultArgs } : {}),\n }\n const ctxOpts = rankCheckContextOptions(config)\n if (config.profileDir) {\n this.context = await chromium.launchPersistentContext(config.profileDir, {\n ...launchOpts,\n ...ctxOpts,\n })\n await this.installEsbuildHelperShims(this.context)\n this.page = await this.context.newPage()\n } else {\n this.browser = await chromium.launch(launchOpts)\n this.context = await this.browser.newContext(ctxOpts)\n await this.installEsbuildHelperShims(this.context)\n this.page = await this.context.newPage()\n }\n if (this.debugEnabled) {\n this.debugSnapshot.networkLocation = await this.captureBrowserNetworkLocation()\n }\n }\n\n private async populateKernelRetrieveDebug(kernelDebug: KernelSessionDebug, requestedProxyId: string | undefined): Promise<void> {\n if (!this.kernelClient || !this.kernelSessionId) return\n try {\n const retrieved = await withTimeout(\n this.kernelClient.browsers.retrieve(this.kernelSessionId),\n 5_000,\n `Kernel session ${this.kernelSessionId} retrieve`,\n ) as { proxy_id?: string; timeout_seconds?: number; stealth?: boolean; profile?: unknown; pool?: unknown }\n kernelDebug.retrievedProxyIdPresent = typeof retrieved.proxy_id === 'string' ? Boolean(retrieved.proxy_id) : false\n kernelDebug.retrievedProxyIdSuffix = proxyIdSuffix(retrieved.proxy_id)\n kernelDebug.retrievedProxyIdMatchesRequested = requestedProxyId\n ? retrieved.proxy_id === requestedProxyId\n : !retrieved.proxy_id\n kernelDebug.timeoutSeconds = typeof retrieved.timeout_seconds === 'number' ? retrieved.timeout_seconds : kernelDebug.timeoutSeconds\n kernelDebug.stealth = typeof retrieved.stealth === 'boolean' ? retrieved.stealth : kernelDebug.stealth\n kernelDebug.profilePresent = Boolean(retrieved.profile)\n kernelDebug.poolPresent = Boolean(retrieved.pool)\n } catch (err) {\n kernelDebug.retrieveError = errorText(err)\n }\n }\n\n private async captureBrowserNetworkLocation(): Promise<BrowserNetworkLocationDebug> {\n const fallback = (message: string, source: BrowserNetworkLocationDebug['source'] = 'ipapi.co'): BrowserNetworkLocationDebug => ({\n source,\n ip: null,\n city: null,\n region: null,\n country: null,\n org: null,\n timezone: null,\n error: message,\n })\n if (!this.context) return fallback('browser context is not available')\n let debugPage: Page | null = null\n try {\n debugPage = await this.context.newPage()\n const ipwho = await this.loadJsonInDebugPage(debugPage, 'https://ipwho.is/')\n if (ipwho) {\n const connection = typeof ipwho.connection === 'object' && ipwho.connection !== null\n ? ipwho.connection as Record<string, unknown>\n : {}\n return {\n source: 'ipwho.is',\n ip: typeof ipwho.ip === 'string' ? ipwho.ip : null,\n city: typeof ipwho.city === 'string' ? ipwho.city : null,\n region: typeof ipwho.region === 'string' ? ipwho.region : null,\n country: typeof ipwho.country === 'string' ? ipwho.country : null,\n org: typeof connection.org === 'string' ? connection.org : null,\n timezone: typeof ipwho.timezone === 'object' && ipwho.timezone !== null && typeof (ipwho.timezone as Record<string, unknown>).id === 'string'\n ? (ipwho.timezone as Record<string, unknown>).id as string\n : null,\n error: null,\n }\n }\n\n const ipify = await this.loadJsonInDebugPage(debugPage, 'https://api64.ipify.org?format=json')\n if (ipify) {\n return {\n source: 'api64.ipify.org',\n ip: typeof ipify.ip === 'string' ? ipify.ip : null,\n city: null,\n region: null,\n country: null,\n org: null,\n timezone: null,\n error: null,\n }\n }\n\n await withTimeout(\n debugPage.goto('https://ipapi.co/json/', { waitUntil: 'domcontentloaded', timeout: 7_000 }),\n 8_000,\n 'browser network location navigation',\n )\n const body = await debugPage.locator('body').innerText({ timeout: 2_000 })\n const data = JSON.parse(body) as Record<string, unknown>\n return {\n source: 'ipapi.co',\n ip: typeof data.ip === 'string' ? data.ip : null,\n city: typeof data.city === 'string' ? data.city : null,\n region: typeof data.region === 'string' ? data.region : null,\n country: typeof data.country_name === 'string' ? data.country_name : typeof data.country === 'string' ? data.country : null,\n org: typeof data.org === 'string' ? data.org : null,\n timezone: typeof data.timezone === 'string' ? data.timezone : null,\n error: null,\n }\n } catch (err) {\n return fallback(errorText(err))\n } finally {\n await debugPage?.close().catch(() => {})\n }\n }\n\n private async loadJsonInDebugPage(debugPage: Page, url: string): Promise<Record<string, unknown> | null> {\n try {\n await withTimeout(\n debugPage.goto(url, { waitUntil: 'domcontentloaded', timeout: 7_000 }),\n 8_000,\n `browser network location navigation ${url}`,\n )\n const body = await debugPage.locator('body').innerText({ timeout: 2_000 })\n return JSON.parse(body) as Record<string, unknown>\n } catch {\n return null\n }\n }\n\n private async installEsbuildHelperShims(context: BrowserContext): Promise<void> {\n await context.addInitScript(() => {\n const g = globalThis as Record<string, unknown>\n if (typeof g.__name !== 'function') g.__name = (fn: unknown) => fn\n if (typeof g.__publicField !== 'function') g.__publicField = (obj: Record<string, unknown>, key: string, value: unknown) => { obj[key] = value; return value }\n })\n }\n\n async navigateToSERP(\n query: string,\n uule: UuleString | null,\n gl: string,\n hl: string,\n options?: { num?: number; debug?: boolean; tbs?: string },\n ): Promise<{ hasPaa: boolean }> {\n const params = new URLSearchParams({ q: query, gl, hl, pws: '0' })\n if (options?.num) params.set('num', String(options.num))\n if (options?.tbs) params.set('tbs', options.tbs)\n if (uule) params.set('uule', uule)\n const url = 'https://www.google.com/search?' + params.toString()\n const navUrl = serpArrivalUrl(url)\n const navDebug: SerpNavigationDebug | null = options?.debug\n ? {\n requestedUrl: url,\n finalUrl: null,\n title: null,\n bodySnippet: null,\n hasPaa: null,\n captchaDetected: null,\n googleSorryUrl: null,\n redirected: null,\n }\n : null\n if (navDebug) this.debugSnapshot.serpNavigation = navDebug\n\n try {\n await this.page!.goto(navUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 })\n } catch (err) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: null, captchaDetected: null })\n const diag = await this.captureDiagnostics(url)\n throw new ExtractionError(`page.goto failed: ${(err as Error).message} | ${diag}`)\n }\n\n if (await this.isSerpChallengeVisible()) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true })\n throw new CaptchaError(this.captchaMessage())\n }\n\n const fastFound = await this.page!.waitForSelector(PAASelectors.item, { timeout: 4_000 }).catch(() => null)\n if (fastFound) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: true, captchaDetected: false })\n return { hasPaa: true }\n }\n\n if (await this.isSerpChallengeVisible()) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: true })\n throw new CaptchaError(this.captchaMessage())\n }\n\n for (let i = 1; i <= 6; i++) {\n await this.page!.evaluate((f: number) => { window.scrollTo(0, document.body.scrollHeight * f) }, i / 6)\n await this.page!.waitForTimeout(600)\n const count = await this.page!.locator(PAASelectors.item).count()\n if (count > 0) {\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: true, captchaDetected: false })\n return { hasPaa: true }\n }\n }\n\n await this.updateSerpNavigationDebug(navDebug, url, { hasPaa: false, captchaDetected: false })\n return { hasPaa: false }\n }\n\n private async isSerpChallengeVisible(): Promise<boolean> {\n if (!this.page) return false\n const captchaCount = await this.page.locator(PAASelectors.captchaMarker).count().catch(() => 0)\n if (captchaCount > 0) return true\n const finalUrl = this.page.url()\n if (/google\\.[^/]+\\/sorry\\//i.test(finalUrl)) return true\n const bodySnippet = await this.page.evaluate(() => {\n const text = (document.body?.innerText ?? '').replace(/\\s+/g, ' ').trim()\n return text.slice(0, 1000)\n }).catch(() => '')\n return /recaptcha|unusual traffic|are you a robot|about this page|detected unusual traffic/i.test(bodySnippet)\n }\n\n private async updateSerpNavigationDebug(\n navDebug: SerpNavigationDebug | null,\n requestedUrl: string,\n state: { hasPaa: boolean | null; captchaDetected: boolean | null },\n ): Promise<void> {\n if (!navDebug || !this.page) return\n try {\n const finalUrl = this.page.url()\n const title = await this.page.title().catch(() => '')\n const bodySnippet = await this.page.evaluate(() => {\n const text = (document.body?.innerText ?? '').replace(/\\s+/g, ' ').trim()\n return text.slice(0, 500)\n }).catch(() => '')\n const textCaptcha = /recaptcha|unusual traffic|are you a robot/i.test(bodySnippet)\n navDebug.finalUrl = finalUrl\n navDebug.title = title\n navDebug.bodySnippet = bodySnippet\n navDebug.hasPaa = state.hasPaa\n navDebug.captchaDetected = state.captchaDetected ?? textCaptcha\n navDebug.googleSorryUrl = /google\\.[^/]+\\/sorry\\//i.test(finalUrl)\n navDebug.redirected = finalUrl !== requestedUrl\n } catch (err) {\n navDebug.bodySnippet = `debug capture failed: ${errorText(err)}`\n }\n }\n\n private async captureDiagnostics(intendedUrl: string): Promise<string> {\n try {\n const finalUrl = this.page!.url()\n const title = await this.page!.title().catch(() => '')\n const bodySnippet = await this.page!.evaluate(() => {\n const t = (document.body?.innerText ?? '').replace(/\\s+/g, ' ').trim()\n return t.slice(0, 400)\n }).catch(() => '')\n const consent = /consent\\.google\\./.test(finalUrl) || /before you continue/i.test(bodySnippet)\n const recaptcha = /recaptcha|unusual traffic|are you a robot/i.test(bodySnippet)\n const flags = [\n consent ? 'CONSENT_WALL' : '',\n recaptcha ? 'BOT_CHALLENGE' : '',\n finalUrl !== intendedUrl ? 'REDIRECTED' : '',\n ].filter(Boolean).join(',')\n return `intended=${intendedUrl} | final=${finalUrl} | title=\"${title}\" | flags=[${flags}] | body=\"${bodySnippet}\"`\n } catch (e) {\n return `diagnostics-failed: ${(e as Error).message}`\n }\n }\n\n private captchaMessage(): string {\n return this.kernelClient\n ? 'Google returned a CAPTCHA on this session — retrying with a fresh session.'\n : RECAPTCHA_INSTRUCTIONS\n }\n\n async navigateTo(url: string): Promise<void> {\n const arrivalBase = process.env.ADS_ARRIVAL_REDIRECT_BASE?.trim()\n const navUrl = arrivalBase ? serpArrivalUrl(url, arrivalBase) : url\n try {\n await this.page!.goto(navUrl, { waitUntil: 'domcontentloaded', timeout: 45_000 })\n if (/google\\.[^/]+\\/search\\?/i.test(url) && await this.isSerpChallengeVisible()) {\n throw new CaptchaError(this.captchaMessage())\n }\n } catch (err) {\n if (err instanceof CaptchaError) throw err\n const diag = await this.captureDiagnostics(url)\n throw new ExtractionError(`page.goto failed: ${(err as Error).message} | ${diag}`)\n }\n }\n\n async navigateToChannel(channelHandle: string): Promise<void> {\n const url = buildYouTubeChannelVideosUrl(channelHandle)\n try {\n await this.page!.goto(url, { waitUntil: 'networkidle', timeout: 30_000 })\n } catch (err) {\n const diag = await this.captureDiagnostics(url)\n throw new ExtractionError(`navigateToChannel failed: ${(err as Error).message} | ${diag}`)\n }\n }\n\n async evaluate<T>(fn: (...args: unknown[]) => T | Promise<T>, arg?: unknown): Promise<T> {\n return (this.page! as { evaluate(fn: unknown, arg?: unknown): Promise<T> }).evaluate(fn, arg)\n }\n\n getPage(): Page {\n return this.page!\n }\n\n getKernelSessionId(): string | null {\n return this.kernelSessionId\n }\n\n getDebugSnapshot(): BrowserDebugSnapshot {\n return this.debugSnapshot\n }\n\n async close(): Promise<BrowserCloseResult> {\n if (this.browser) {\n const b = this.browser\n const sessionId = this.kernelSessionId\n const client = this.kernelClient\n const openedAtMs = this.kernelOpenedAtMs\n const stealth = this.kernelStealth\n const proxyUsed = this.kernelProxyUsed\n const proxySource = this.kernelProxySource\n const proxyType = this.kernelProxyType\n const headlessSent = this.kernelHeadlessSent\n this.browser = null\n this.context = null\n this.page = null\n this.kernelSessionId = null\n this.kernelClient = null\n this.kernelOpenedAtMs = null\n this.kernelHeadlessSent = null\n this.kernelProxySource = null\n this.kernelProxyType = null\n if (client && sessionId && openedAtMs != null) {\n void recordKernelSession({ kernelSessionId: sessionId, source: 'browser_driver', stealth, headlessSent, proxyUsed, proxySource, proxyType, openedAtMs, closedAtMs: Date.now() })\n }\n if (client && sessionId) {\n console.info(JSON.stringify({\n event: 'kernel_browser_delete_started',\n kernel_session_id: sessionId,\n }))\n const deleteSession = withTimeout(\n client.browsers.deleteByID(sessionId),\n KERNEL_SESSION_DELETE_TIMEOUT_MS,\n `Kernel session ${sessionId} delete`,\n )\n const closeBrowser = withTimeout(\n b.close(),\n KERNEL_BROWSER_CLOSE_TIMEOUT_MS,\n `Kernel browser ${sessionId} close`,\n )\n const [deleteResult, closeResult] = await Promise.allSettled([deleteSession, closeBrowser])\n const result: BrowserCloseResult = {\n kernelSessionId: sessionId,\n kernelDeleteStarted: true,\n kernelDeleteSucceeded: deleteResult.status === 'fulfilled',\n kernelDeleteError: deleteResult.status === 'rejected'\n ? (deleteResult.reason instanceof Error ? deleteResult.reason.message : String(deleteResult.reason))\n : null,\n browserCloseSucceeded: closeResult.status === 'fulfilled',\n browserCloseError: closeResult.status === 'rejected'\n ? (closeResult.reason instanceof Error ? closeResult.reason.message : String(closeResult.reason))\n : null,\n }\n if (deleteResult.status === 'rejected') {\n console.warn(JSON.stringify({\n event: 'kernel_browser_delete_failed',\n kernel_session_id: sessionId,\n message: result.kernelDeleteError,\n }))\n console.warn(`Kernel session cleanup failed for ${sessionId}:`, deleteResult.reason)\n } else {\n console.info(JSON.stringify({\n event: 'kernel_browser_delete_succeeded',\n kernel_session_id: sessionId,\n }))\n }\n if (closeResult.status === 'rejected') {\n console.warn(JSON.stringify({\n event: 'kernel_browser_close_failed',\n kernel_session_id: sessionId,\n message: result.browserCloseError,\n }))\n console.warn(`Kernel browser close failed for ${sessionId}:`, closeResult.reason)\n }\n return result\n }\n await b.close()\n return {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: true,\n browserCloseError: null,\n }\n } else if (this.context) {\n const ctx = this.context\n this.context = null\n this.page = null\n await ctx.close()\n return {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: true,\n browserCloseError: null,\n }\n }\n return {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: null,\n browserCloseError: null,\n }\n }\n}\n","export const PAASelectors = {\n container: '.eJH8qe.adDDi',\n dataInitq: '[data-initq]',\n item: '.related-question-pair',\n itemDataQ: 'data-q',\n itemDataInitQ: 'data-initq',\n itemQuestionEl: '.JlqpRe',\n answerContainer: '.bCOlv, .hgKElc, .wDYxhc, .LGOjhe, .fo7IQd, .fmW3u',\n aiAnswerBody: '.pWvJNd',\n aiCitation: 'a.PMDqCb[href]',\n aiSourceCard: 'a.vIWmYe[href], a.Zbfntb[href]',\n sourceTitle: 'h3',\n sourceSite: '.VuuXrf',\n sourceCite: 'cite',\n clickTarget: '.JlqpRe',\n expandedClass: 'aoRk1c',\n captchaMarker: '#captcha-form, #recaptcha, form[action*=\"/sorry/\"], .g-recaptcha, [data-sitekey]',\n} as const\n\nexport const VideoSelectors = {\n container: 'div[jscontroller=\"HWk0Gf\"]',\n sectionHeading: '.mgAbYb[role=\"heading\"]',\n item: 'a.rIRoqf',\n} as const\n\nexport const ShortVideoSelectors = {\n udm: '39',\n item: 'a.rIRoqf',\n durationPattern: /^\\d+:\\d+$/,\n platforms: ['YouTube', 'TikTok', 'Instagram', 'Facebook', 'X'],\n} as const\n\nexport const ForumSelectors = {\n section: '.ULSxyf',\n item: 'a.KYg7td.INpicf',\n title: '.hyYc0c',\n source: '.K4ETW',\n} as const\n\nexport const WhatPeopleSayingSelectors = {\n sectionTag: 'g-section-with-header',\n sectionHeadingText: 'What people are saying',\n card: '.dRzkFf[role=\"listitem\"]',\n cardLink: 'a.WlydOe[jsname=\"YKoRaf\"]',\n titleH1: 'h1.WQWxe',\n titleDiv: '.eAaXgc',\n popularCommentLabel: '.qgdis',\n source: '.sTl1Td',\n platformBadge: '.appd0, .KrMNbf',\n ytChannel: '.sjVJQd',\n ytDate: '.PLq9Je',\n authorNote: '.nDgy9d',\n} as const\n\nexport const AIOverviewSelectors = {\n root: '[data-lhcontainer][data-streaming-container][eid]',\n legacyRoot: '[data-hveid=\"CBMQAA\"]',\n wrapper: '.Fgyi2e',\n controller: '[jscontroller=\"AkrxPe\"]',\n contentSubtree: '[data-subtree=\"mfc\"]',\n header: '.heWuVc',\n heading: '.Fzsovc.cwYVJe.RJPOee',\n showMoreButton: '[aria-label=\"Show more AI Overview\"]',\n sourcesPanel: '.OZ9ddf.WAUd4',\n disclaimer: '.DuQANe.MSJHRb',\n} as const\n\nexport const AIModeSelectors = {\n root: '[data-hveid=\"CAUQAA\"]',\n wrapper: '.Fgyi2e',\n citations: '.Fgyi2e [data-hveid] a[jsname=\"pxBnId\"]',\n} as const\n\nexport const OrganicSelectors = {\n result: '.wHYlTd.tF2Cxc',\n title: 'h3.LC20lb',\n siteName: '.VuuXrf',\n cite: 'cite.tjvcx',\n snippet: '.VwiC3b',\n redditCite: 'cite.qLRx3b',\n ratingWrap: '.Y0A0hc',\n ratingValue: '.yi40Hd',\n reviewCount: '.RDApEe',\n} as const\n\nexport const LocalPackSelectors = {\n headingText: 'Businesses',\n card: '.w7Dbne',\n name: '.OSrXXb',\n ratingValue: '.yi40Hd',\n reviewCount: '.RDApEe',\n} as const\n\nexport const SELECTORS_VERSION = '2026-05-07'\n\nexport const MapsSelectors = {\n ratingAndCount: 'div.F7nice',\n hoursTable: 'table.eK4R0e',\n reviewScrollPane: 'div.m6QErb[tabindex=\"-1\"]',\n reviewScrollPaneFallback: '[role=\"main\"] div[tabindex=\"-1\"]',\n reviewCardAuthor: 'div.d4r55, span.d4r55, span.RPZfBb',\n reviewCardDate: 'span.rsqaWe',\n reviewCardText: 'span.wiI7pd',\n reviewCardOwnerBlock: 'div.CDe7pd',\n hoursTableAlt: 'table[aria-label*=\"Hour\"]',\n reviewCard: '[data-review-id]',\n reviewStars: '[role=\"img\"][aria-label*=\"star\"]',\n reviewTab: 'button[role=\"tab\"][aria-label*=\"Review\"]',\n aboutTab: 'button[role=\"tab\"][aria-label*=\"About\"]',\n expandReview: '[data-review-id] button[aria-label*=\"See more\"], [data-review-id] button.w8nwRe',\n} as const\n\nexport const MAPS_SELECTORS_VERSION = '2026-05-23'\n","export const RECAPTCHA_INSTRUCTIONS = 'Google returned a CAPTCHA. Run with --headless=false to re-warm the browser profile, then retry.'\nexport const PUBLIC_SERVICE_UNAVAILABLE_MESSAGE = 'This operation is temporarily unavailable. Please retry later.'\n\nconst PUBLIC_ERROR_CODES = new Set([\n 'request_aborted',\n 'captcha_exhausted',\n 'captcha_or_blocked',\n 'location_mismatch',\n 'proxy_tunnel_failed',\n 'proxy_unavailable',\n 'harvest_timeout',\n 'mcp_request_timeout',\n 'service_unavailable',\n])\n\nexport function normalizePublicErrorCode(code: unknown): string {\n return typeof code === 'string' && PUBLIC_ERROR_CODES.has(code)\n ? code\n : 'service_unavailable'\n}\n\nexport function publicErrorMessage(code: unknown): string {\n switch (normalizePublicErrorCode(code)) {\n case 'request_aborted':\n return 'The request was cancelled before it completed.'\n case 'captcha_exhausted':\n case 'captcha_or_blocked':\n return 'The target site returned a verification challenge. Please retry later.'\n case 'location_mismatch':\n return 'The target site returned results for a different location. Please retry.'\n case 'proxy_tunnel_failed':\n case 'proxy_unavailable':\n return 'The web request could not establish a working connection. Please retry later.'\n case 'harvest_timeout':\n case 'mcp_request_timeout':\n return 'The request timed out before it completed. Please retry with a smaller request.'\n default:\n return PUBLIC_SERVICE_UNAVAILABLE_MESSAGE\n }\n}\n\nexport function sanitizeVendorName(message: string): string {\n return message\n .replace(/kernel\\.sh\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\.sh\\s+session/gi, 'this session')\n .replace(/kernel\\.sh/gi, 'the service')\n .replace(/kernel\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\s+session/gi, 'this session')\n .replace(/\\bkernel\\b/gi, 'the service')\n .replace(/ +/g, ' ')\n .trim()\n}\n\nexport class CaptchaError extends Error {\n readonly name = 'CaptchaError'\n constructor(public readonly instructions: string) {\n super(`CAPTCHA detected. ${instructions}`)\n }\n}\n\nexport class ExtractionError extends Error {\n readonly name = 'ExtractionError'\n constructor(message: string, public readonly cause?: unknown) {\n super(message)\n }\n}\n\n/**\n * Google Maps sometimes finishes navigation without rendering either result\n * cards or its explicit no-results message. Treating that page as a genuine\n * empty search loses data, so callers should retry it with a fresh browser (and\n * a fresh proxy when location rotation is enabled).\n */\nexport class MapsSearchIncompletePageError extends Error {\n readonly name = 'MapsSearchIncompletePageError'\n\n constructor(public readonly pageState: string) {\n super(`Google Maps search did not reach a complete result state (${pageState})`)\n }\n}\n\nexport class RequestAbortedError extends Error {\n readonly name = 'RequestAbortedError'\n constructor(message = 'Request aborted before harvest completed') {\n super(message)\n }\n}\n\nexport class LocationMismatchError extends Error {\n readonly name = 'LocationMismatchError'\n constructor(message = 'Google returned results for a different location than requested') {\n super(message)\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport { getDb } from './db.js'\nimport { currentCostContext } from './cost-context.js'\nimport { kernelCostUsd, vendorCostUsd } from './cost-rates.js'\n\nlet _ready = false\nlet _migrationPromise: Promise<void> | null = null\n\nexport async function migrateCostTelemetry(): Promise<void> {\n if (_ready) return\n if (_migrationPromise) return _migrationPromise\n _migrationPromise = runCostTelemetryMigration().finally(() => {\n _migrationPromise = null\n })\n return _migrationPromise\n}\n\nasync function runCostTelemetryMigration(): Promise<void> {\n const db = getDb()\n const existing = await db.execute(`\n SELECT\n (SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('kernel_session_log', 'vendor_usage_log', 'cost_probe_runs')) = 3\n AND (SELECT COUNT(*) FROM pragma_table_info('kernel_session_log') WHERE name IN ('proxy_source', 'proxy_type', 'method')) = 3\n AND (SELECT COUNT(*) FROM pragma_table_info('vendor_usage_log') WHERE name = 'method') = 1\n AND (SELECT COUNT(*) FROM pragma_table_info('cost_probe_runs') WHERE name IN ('units', 'unit_type', 'mode')) = 3\n AS ready\n `)\n if (Number(existing.rows[0]?.ready ?? 0) === 1) {\n _ready = true\n return\n }\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kernel_session_log (\n id TEXT PRIMARY KEY,\n kernel_session_id TEXT,\n op TEXT,\n source TEXT NOT NULL,\n probe_run_id TEXT,\n user_id INTEGER,\n stealth INTEGER,\n headless_sent INTEGER,\n proxy_used INTEGER,\n fallback INTEGER NOT NULL DEFAULT 0,\n opened_at TEXT,\n closed_at TEXT,\n duration_ms INTEGER NOT NULL DEFAULT 0,\n est_cost_usd_headless REAL NOT NULL DEFAULT 0,\n est_cost_usd_headful REAL NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`)\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN method TEXT`) } catch { void 0 }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS vendor_usage_log (\n id TEXT PRIMARY KEY,\n op TEXT,\n probe_run_id TEXT,\n user_id INTEGER,\n vendor TEXT NOT NULL,\n model TEXT,\n units REAL NOT NULL DEFAULT 0,\n unit_type TEXT,\n est_cost_usd REAL NOT NULL DEFAULT 0,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`)\n try { await db.execute(`ALTER TABLE vendor_usage_log ADD COLUMN method TEXT`) } catch { void 0 }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS cost_probe_runs (\n id TEXT PRIMARY KEY,\n tool TEXT NOT NULL,\n target TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n wall_ms INTEGER NOT NULL DEFAULT 0,\n success INTEGER NOT NULL DEFAULT 0,\n error TEXT,\n http_only INTEGER NOT NULL DEFAULT 0,\n kernel_used INTEGER NOT NULL DEFAULT 0,\n kernel_sessions INTEGER NOT NULL DEFAULT 0,\n kernel_seconds_total REAL NOT NULL DEFAULT 0,\n kernel_tier_observed TEXT,\n est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,\n est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,\n vendor_cost_usd REAL NOT NULL DEFAULT 0,\n charged_credits REAL NOT NULL DEFAULT 0,\n margin_usd_headless_starter REAL NOT NULL DEFAULT 0,\n margin_usd_headful_starter REAL NOT NULL DEFAULT 0,\n notes TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`)\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`) } catch { void 0 }\n try { await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`) } catch { void 0 }\n _ready = true\n}\n\nexport interface KernelSessionRecord {\n kernelSessionId?: string | null\n source: string\n stealth?: boolean | null\n headlessSent?: boolean | null\n proxyUsed?: boolean | null\n proxySource?: string | null\n proxyType?: string | null\n fallback?: boolean\n openedAtMs: number\n closedAtMs: number\n error?: string | null\n}\n\nexport async function recordKernelSession(r: KernelSessionRecord): Promise<void> {\n try {\n await migrateCostTelemetry()\n const ctx = currentCostContext()\n const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs)\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO kernel_session_log\n (id, kernel_session_id, op, source, probe_run_id, user_id, stealth, headless_sent, proxy_used, proxy_source, proxy_type, fallback, opened_at, closed_at, duration_ms, est_cost_usd_headless, est_cost_usd_headful, error, method)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n randomUUID(),\n r.kernelSessionId ?? null,\n ctx?.op ?? null,\n r.source,\n ctx?.probeRunId ?? null,\n ctx?.userId ?? null,\n boolToInt(r.stealth),\n boolToInt(r.headlessSent),\n boolToInt(r.proxyUsed),\n r.proxySource ?? null,\n r.proxyType ?? null,\n r.fallback ? 1 : 0,\n new Date(r.openedAtMs).toISOString(),\n new Date(r.closedAtMs).toISOString(),\n durationMs,\n kernelCostUsd(durationMs, false),\n kernelCostUsd(durationMs, true),\n r.error ?? null,\n ctx?.subOp ?? null,\n ],\n })\n } catch (err) {\n console.warn('[cost-telemetry] recordKernelSession failed:', err instanceof Error ? err.message : String(err))\n }\n}\n\nexport interface VendorUsageRecord {\n vendor: string\n model?: string | null\n units: number\n unitType: string\n error?: string | null\n}\n\nexport async function recordVendorUsage(r: VendorUsageRecord): Promise<void> {\n try {\n await migrateCostTelemetry()\n const ctx = currentCostContext()\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO vendor_usage_log\n (id, op, probe_run_id, user_id, vendor, model, units, unit_type, est_cost_usd, error, method)\n VALUES (?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n randomUUID(),\n ctx?.op ?? null,\n ctx?.probeRunId ?? null,\n ctx?.userId ?? null,\n r.vendor,\n r.model ?? null,\n r.units,\n r.unitType,\n vendorCostUsd(r.vendor, r.units),\n r.error ?? null,\n ctx?.subOp ?? null,\n ],\n })\n } catch (err) {\n console.warn('[cost-telemetry] recordVendorUsage failed:', err instanceof Error ? err.message : String(err))\n }\n}\n\nexport interface ProbeRunRecord {\n id: string\n tool: string\n target?: string | null\n wallMs: number\n success: boolean\n error?: string | null\n httpOnly: boolean\n kernelUsed: boolean\n kernelSessions: number\n kernelSecondsTotal: number\n kernelTierObserved?: string | null\n estKernelCostUsdHeadless: number\n estKernelCostUsdHeadful: number\n vendorCostUsd: number\n chargedCredits: number\n marginUsdHeadlessStarter: number\n marginUsdHeadfulStarter: number\n units?: number | null\n unitType?: string | null\n mode?: string | null\n notes?: string | null\n}\n\nexport async function recordProbeRun(r: ProbeRunRecord): Promise<void> {\n await migrateCostTelemetry()\n const db = getDb()\n await db.execute({\n sql: `INSERT INTO cost_probe_runs\n (id, tool, target, wall_ms, success, error, http_only, kernel_used, kernel_sessions, kernel_seconds_total, kernel_tier_observed, est_kernel_cost_usd_headless, est_kernel_cost_usd_headful, vendor_cost_usd, charged_credits, margin_usd_headless_starter, margin_usd_headful_starter, units, unit_type, mode, notes)\n VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n args: [\n r.id, r.tool, r.target ?? null, r.wallMs, r.success ? 1 : 0, r.error ?? null,\n r.httpOnly ? 1 : 0, r.kernelUsed ? 1 : 0, r.kernelSessions, r.kernelSecondsTotal,\n r.kernelTierObserved ?? null, r.estKernelCostUsdHeadless, r.estKernelCostUsdHeadful,\n r.vendorCostUsd, r.chargedCredits, r.marginUsdHeadlessStarter, r.marginUsdHeadfulStarter,\n r.units ?? null, r.unitType ?? null, r.mode ?? null, r.notes ?? null,\n ],\n })\n}\n\nfunction boolToInt(v: boolean | null | undefined): number | null {\n if (v === null || v === undefined) return null\n return v ? 1 : 0\n}\n","import { createClient, type Client } from '@libsql/client/http'\nimport { createHash, randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'node:crypto'\nimport { z } from 'zod'\nimport { CREDIT_LOTS_EPOCH } from './credit-lot-integrity.js'\n\nexport { CREDIT_LOTS_EPOCH } from './credit-lot-integrity.js'\n\nconst DB_URL = process.env.TURSO_DATABASE_URL ?? 'file:./paa-api.db'\nconst DB_TOKEN = process.env.TURSO_AUTH_TOKEN\n\nlet _db: Client | null = null\nlet _rateLimitSchemaReady = false\n\nexport const CORE_SCHEMA_VERSION = '2026-07-28.5'\nconst CORE_SCHEMA_TABLES = [\n 'admin_credit_adjustment_events',\n 'admin_credit_adjustments',\n 'billing_authorizations',\n 'billing_credits',\n 'billing_debits',\n 'billing_debit_allocations',\n 'billing_events',\n 'browser_profile_labels',\n 'connected_account_billing',\n 'concurrency_locks',\n 'credit_lot_expirations',\n 'credit_lots',\n 'credit_lot_repairs',\n 'free_credit_refreshes',\n 'harvest_attempts',\n 'inbox_messages',\n 'jobs',\n 'kpo_jobs',\n 'kpo_phase_log',\n 'ledger',\n 'oauth_clients',\n 'oauth_codes',\n 'oauth_tokens',\n 'page_snapshots',\n 'password_reset_tokens',\n 'rate_limits',\n 'request_events',\n 'site_audit_jobs',\n 'site_audit_phase_log',\n 'site_extract_jobs',\n 'site_extract_pages',\n 'stripe_events',\n 'users',\n 'workflow_artifacts',\n 'workflow_runs',\n 'workflow_schedules',\n 'workflow_webhook_deliveries',\n 'workshop_registrations',\n] as const\n\nconst CORE_SCHEMA_INDEXES = [\n 'admin_credit_adjustment_events_reference',\n 'admin_credit_adjustments_user_created',\n 'billing_authorizations_user_status',\n 'billing_credits_user_status',\n 'billing_debits_user_status',\n 'billing_events_authorization',\n 'billing_events_user_created_at',\n 'concurrency_locks_operation',\n 'concurrency_locks_user_status',\n 'credit_lots_user_active',\n 'credit_lot_repairs_user_applied',\n 'harvest_attempts_job_id',\n 'harvest_attempts_user_created_at',\n 'inbox_messages_user_sent_at',\n 'jobs_created_at_status_user',\n 'jobs_status',\n 'jobs_user_created_at_status',\n 'jobs_user_id',\n 'kpo_jobs_status',\n 'kpo_jobs_user_id',\n 'kpo_phase_log_job_id',\n 'ledger_stripe_pi_unique',\n 'ledger_user_id',\n 'oauth_tokens_identity',\n 'page_snapshots_user_checked_at',\n 'prt_user_id',\n 'request_events_user_created_at',\n 'site_audit_jobs_status',\n 'site_audit_jobs_user_id',\n 'site_audit_phase_log_job_id',\n 'site_extract_jobs_status',\n 'site_extract_jobs_idempotency',\n 'site_extract_jobs_user_id',\n 'site_extract_pages_job_id',\n 'workflow_artifacts_run',\n 'workflow_runs_idempotency',\n 'workflow_runs_schedule',\n 'workflow_runs_user_created',\n 'workflow_schedules_due',\n 'workflow_schedules_user_status',\n 'workflow_webhook_deliveries_run',\n 'workshop_registrations_event_status',\n] as const\n\nconst CORE_SCHEMA_TRIGGERS = [\n 'credit_lots_bounds_insert',\n 'credit_lots_bounds_update',\n 'credit_lots_one_migration_insert',\n 'credit_lots_one_migration_update',\n] as const\n\nconst CORE_SCHEMA_COLUMNS = {\n credit_lots: ['never_expires'],\n harvest_attempts: ['debug_json'],\n oauth_tokens: ['replaced_by', 'rotated_at'],\n site_extract_jobs: [\n 'attempted_urls', 'successful_urls', 'failed_urls', 'idempotency_key', 'request_fingerprint',\n 'dispatch_attempts', 'next_dispatch_at', 'dispatch_error',\n 'settlement_attempts', 'next_settlement_at', 'settlement_error',\n ],\n stripe_events: ['attempt_count', 'claim_token', 'last_error', 'status', 'updated_at'],\n users: [\n 'balance_mc',\n 'concurrency_stripe_sub_id',\n 'extra_concurrency_slots',\n 'key_active',\n 'memory_identity',\n 'memory_key',\n 'memory_plan',\n 'memory_provisioned',\n 'memory_subscription_id',\n 'password_hash',\n 'stripe_customer_id',\n 'subscription_concurrency',\n 'subscription_id',\n 'subscription_tier',\n ],\n workshop_registrations: ['field_notes_email_id', 'reminder_48h_email_id', 'reminder_24h_email_id', 'reminder_1h_email_id'],\n workflow_artifacts: ['content'],\n workflow_runs: ['step_index', 'step_log_json', 'step_state_json', 'total_steps', 'webhook_url'],\n} as const\n\nfunction sqlStringList(values: readonly string[]): string {\n return values.map(value => `'${value.replaceAll(\"'\", \"''\")}'`).join(', ')\n}\n\nasync function hasCurrentCoreSchema(db: Client): Promise<boolean> {\n const checks = [\n `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN (${sqlStringList(CORE_SCHEMA_TABLES)})) = ${CORE_SCHEMA_TABLES.length}`,\n `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name IN (${sqlStringList(CORE_SCHEMA_INDEXES)})) = ${CORE_SCHEMA_INDEXES.length}`,\n `(SELECT COUNT(*) FROM sqlite_master WHERE type = 'trigger' AND name IN (${sqlStringList(CORE_SCHEMA_TRIGGERS)})) = ${CORE_SCHEMA_TRIGGERS.length}`,\n ...Object.entries(CORE_SCHEMA_COLUMNS).map(([table, columns]) =>\n `(SELECT COUNT(*) FROM pragma_table_info('${table}') WHERE name IN (${sqlStringList(columns)})) = ${columns.length}`,\n ),\n ]\n const result = await db.execute(`SELECT ${checks.join(' AND ')} AS ready`)\n return Number(result.rows[0]?.ready ?? 0) === 1\n}\n\n/**\n * Data repairs that must run even when every structural schema check already\n * passes. Keeping them outside the DDL path means a crash after ALTER TABLE but\n * before the repair cannot cause the repair to be skipped forever.\n */\nasync function runIdempotentCoreDataBackfills(db: Client): Promise<void> {\n await db.execute(`\n UPDATE site_extract_jobs SET\n attempted_urls = done_urls,\n successful_urls = (\n SELECT COUNT(*) FROM site_extract_pages\n WHERE job_id = site_extract_jobs.id\n AND CASE\n WHEN json_valid(page) = 0 THEN 0\n WHEN json_extract(page, '$.extractionStatus') IS NOT NULL\n THEN json_extract(page, '$.extractionStatus') = 'successful'\n ELSE COALESCE(\n json_extract(page, '$.status') >= 200\n AND json_extract(page, '$.status') < 300\n AND json_extract(page, '$.wordCount') > 0,\n 0\n )\n END\n ),\n failed_urls = MAX(0, done_urls - (\n SELECT COUNT(*) FROM site_extract_pages\n WHERE job_id = site_extract_jobs.id\n AND CASE\n WHEN json_valid(page) = 0 THEN 0\n WHEN json_extract(page, '$.extractionStatus') IS NOT NULL\n THEN json_extract(page, '$.extractionStatus') = 'successful'\n ELSE COALESCE(\n json_extract(page, '$.status') >= 200\n AND json_extract(page, '$.status') < 300\n AND json_extract(page, '$.wordCount') > 0,\n 0\n )\n END\n ))\n WHERE done_urls > 0\n AND attempted_urls = 0\n AND successful_urls = 0\n AND failed_urls = 0\n `)\n}\n\nexport function getDb(): Client {\n if (!_db) _db = createClient({ url: DB_URL, authToken: DB_TOKEN })\n return _db\n}\n\nexport function setDbForTesting(client: Client | null): void {\n _db = client\n}\n\nexport async function migrate(): Promise<void> {\n const db = getDb()\n let applied\n try {\n applied = await db.execute({\n sql: 'SELECT 1 AS applied FROM schema_migrations WHERE version = ? LIMIT 1',\n args: [CORE_SCHEMA_VERSION],\n })\n } catch (error) {\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()\n if (!message.includes('no such table') || !message.includes('schema_migrations')) throw error\n await db.execute(`\n CREATE TABLE IF NOT EXISTS schema_migrations (\n version TEXT PRIMARY KEY,\n applied_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n applied = { rows: [] }\n }\n if (applied.rows.length > 0 && await hasCurrentCoreSchema(db)) {\n await runIdempotentCoreDataBackfills(db)\n return\n }\n\n // Existing production databases predate the migration registry. Verify the\n // complete legacy schema in one query and register it without replaying 84\n // remote DDL statements on every serverless cold start.\n if (await hasCurrentCoreSchema(db)) {\n await runIdempotentCoreDataBackfills(db)\n await db.execute({\n sql: 'INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)',\n args: [CORE_SCHEMA_VERSION],\n })\n return\n }\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n email TEXT UNIQUE NOT NULL,\n name TEXT,\n api_key TEXT UNIQUE NOT NULL,\n password_hash TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n active INTEGER NOT NULL DEFAULT 1\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n status TEXT NOT NULL DEFAULT 'pending',\n query TEXT NOT NULL,\n options TEXT NOT NULL,\n callback_url TEXT,\n result TEXT,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n started_at TEXT,\n completed_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS jobs_user_id ON jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS jobs_status ON jobs(status)`)\n await db.execute(\n `CREATE INDEX IF NOT EXISTS jobs_created_at_status_user ON jobs(created_at, status, user_id)`,\n )\n await db.execute(\n `CREATE INDEX IF NOT EXISTS jobs_user_created_at_status ON jobs(user_id, created_at, status)`,\n )\n await db.execute(`\n CREATE TABLE IF NOT EXISTS harvest_attempts (\n id TEXT PRIMARY KEY,\n job_id TEXT NOT NULL REFERENCES jobs(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n attempt_number INTEGER NOT NULL,\n max_attempts INTEGER NOT NULL,\n query TEXT NOT NULL,\n location TEXT,\n max_questions INTEGER NOT NULL,\n status TEXT NOT NULL,\n outcome TEXT,\n kernel_session_id TEXT,\n question_count INTEGER,\n duration_ms INTEGER,\n error TEXT,\n will_retry INTEGER,\n kernel_delete_started INTEGER NOT NULL DEFAULT 0,\n kernel_delete_succeeded INTEGER,\n kernel_delete_error TEXT,\n browser_close_succeeded INTEGER,\n browser_close_error TEXT,\n debug_json TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT,\n UNIQUE(job_id, attempt_number)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS harvest_attempts_job_id ON harvest_attempts(job_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS harvest_attempts_user_created_at ON harvest_attempts(user_id, created_at DESC)`)\n try { await db.execute(`ALTER TABLE harvest_attempts ADD COLUMN debug_json TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN password_hash TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN key_active INTEGER NOT NULL DEFAULT 1`) } catch {}\n\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kpo_jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n status TEXT NOT NULL,\n entity_name TEXT NOT NULL,\n entity_type TEXT NOT NULL,\n url TEXT,\n request TEXT NOT NULL,\n result TEXT,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS kpo_phase_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT NOT NULL REFERENCES kpo_jobs(id),\n phase TEXT NOT NULL,\n output TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS kpo_jobs_user_id ON kpo_jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kpo_jobs_status ON kpo_jobs(status)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS kpo_phase_log_job_id ON kpo_phase_log(job_id)`)\n try { await db.execute(`ALTER TABLE users ADD COLUMN stripe_customer_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN balance_mc INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN extra_concurrency_slots INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN concurrency_stripe_sub_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN subscription_concurrency INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN subscription_tier TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN subscription_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_key TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_identity TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_plan TEXT NOT NULL DEFAULT 'free'`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_subscription_id TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE users ADD COLUMN memory_provisioned INTEGER NOT NULL DEFAULT 0`) } catch {}\n await db.execute(`\n CREATE TABLE IF NOT EXISTS concurrency_locks (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n operation TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'active',\n acquired_at TEXT NOT NULL DEFAULT (datetime('now')),\n expires_at TEXT NOT NULL,\n released_at TEXT,\n metadata TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_user_status ON concurrency_locks(user_id, status, expires_at)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS concurrency_locks_operation ON concurrency_locks(operation, status, expires_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS ledger (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n operation TEXT NOT NULL,\n description TEXT,\n stripe_pi TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS ledger_user_id ON ledger(user_id)`)\n await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS ledger_stripe_pi_unique ON ledger(stripe_pi) WHERE stripe_pi IS NOT NULL`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS credit_lots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n remaining_mc INTEGER NOT NULL,\n source TEXT NOT NULL,\n stripe_pi TEXT,\n granted_at TEXT NOT NULL DEFAULT (datetime('now')),\n expires_at TEXT NOT NULL,\n never_expires INTEGER NOT NULL DEFAULT 0\n )\n `)\n try { await db.execute(`ALTER TABLE credit_lots ADD COLUMN never_expires INTEGER NOT NULL DEFAULT 0`) } catch {}\n await db.execute(`CREATE INDEX IF NOT EXISTS credit_lots_user_active ON credit_lots(user_id, expires_at, remaining_mc)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS credit_lot_expirations (\n lot_id INTEGER PRIMARY KEY REFERENCES credit_lots(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n claim_token TEXT NOT NULL UNIQUE,\n expired_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_bounds_insert\n BEFORE INSERT ON credit_lots\n WHEN NEW.amount_mc < 0\n OR NEW.remaining_mc < 0\n OR NEW.remaining_mc > NEW.amount_mc\n BEGIN\n SELECT RAISE(ABORT, 'invalid credit lot bounds');\n END\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_bounds_update\n BEFORE UPDATE OF amount_mc, remaining_mc ON credit_lots\n WHEN NEW.amount_mc < 0\n OR NEW.remaining_mc < 0\n OR NEW.remaining_mc > NEW.amount_mc\n BEGIN\n SELECT RAISE(ABORT, 'invalid credit lot bounds');\n END\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_one_migration_insert\n BEFORE INSERT ON credit_lots\n WHEN NEW.source = 'migration'\n AND EXISTS (\n SELECT 1 FROM credit_lots\n WHERE user_id = NEW.user_id AND source = 'migration'\n )\n BEGIN\n SELECT RAISE(ABORT, 'duplicate migration lot');\n END\n `)\n await db.execute(`\n CREATE TRIGGER IF NOT EXISTS credit_lots_one_migration_update\n BEFORE UPDATE OF source, user_id ON credit_lots\n WHEN NEW.source = 'migration'\n AND EXISTS (\n SELECT 1 FROM credit_lots\n WHERE user_id = NEW.user_id\n AND source = 'migration'\n AND id != OLD.id\n )\n BEGIN\n SELECT RAISE(ABORT, 'duplicate migration lot');\n END\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS credit_lot_repairs (\n repair_id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n report_sha256 TEXT NOT NULL,\n classification TEXT NOT NULL,\n before_json TEXT NOT NULL,\n plan_json TEXT NOT NULL,\n after_json TEXT,\n status TEXT NOT NULL DEFAULT 'claimed',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n applied_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS credit_lot_repairs_user_applied ON credit_lot_repairs(user_id, applied_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS free_credit_refreshes (\n user_id INTEGER NOT NULL REFERENCES users(id),\n month TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (user_id, month)\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_authorizations (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n idempotency_key TEXT NOT NULL UNIQUE,\n billing_class TEXT NOT NULL,\n status TEXT NOT NULL,\n minimum_amount_mc INTEGER NOT NULL,\n maximum_amount_mc INTEGER NOT NULL,\n token_hash TEXT NOT NULL,\n metadata TEXT,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n settled_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_authorizations_user_status ON billing_authorizations(user_id, status, expires_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_events (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n authorization_id TEXT REFERENCES billing_authorizations(id),\n idempotency_key TEXT NOT NULL UNIQUE,\n billing_class TEXT NOT NULL,\n source_surface TEXT NOT NULL,\n status TEXT NOT NULL,\n amount_mc INTEGER NOT NULL DEFAULT 0,\n raw_cost_usd_nanos INTEGER,\n multiplier_bps INTEGER,\n input_tokens INTEGER,\n output_tokens INTEGER,\n cached_tokens INTEGER,\n model_steps INTEGER,\n metadata TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n settled_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_events_user_created_at ON billing_events(user_id, created_at DESC)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_events_authorization ON billing_events(authorization_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_debits (\n idempotency_key TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n operation TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'claimed',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n applied_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_debits_user_status ON billing_debits(user_id, status)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_debit_allocations (\n debit_idempotency_key TEXT NOT NULL REFERENCES billing_debits(idempotency_key),\n lot_id INTEGER NOT NULL REFERENCES credit_lots(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n expires_at TEXT NOT NULL,\n PRIMARY KEY (debit_idempotency_key, lot_id)\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS connected_account_billing (\n user_id INTEGER PRIMARY KEY REFERENCES users(id),\n stripe_subscription_id TEXT,\n stripe_subscription_item_id TEXT,\n price_id TEXT,\n quantity INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL DEFAULT 'pending',\n last_error_code TEXT,\n checked_at TEXT NOT NULL DEFAULT (datetime('now')),\n synced_at TEXT,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS billing_credits (\n idempotency_key TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n amount_mc INTEGER NOT NULL,\n operation TEXT NOT NULL,\n description TEXT,\n status TEXT NOT NULL DEFAULT 'claimed',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n applied_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS billing_credits_user_status ON billing_credits(user_id, status)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS admin_credit_adjustments (\n reference TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n direction TEXT NOT NULL,\n amount_mc INTEGER NOT NULL,\n reason TEXT NOT NULL,\n actor TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'applied',\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS admin_credit_adjustments_user_created ON admin_credit_adjustments(user_id, created_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS admin_credit_adjustment_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n reference TEXT NOT NULL REFERENCES admin_credit_adjustments(reference),\n seq INTEGER NOT NULL,\n kind TEXT NOT NULL,\n from_amount_mc INTEGER,\n to_amount_mc INTEGER,\n from_reason TEXT,\n to_reason TEXT,\n moved_mc INTEGER NOT NULL DEFAULT 0,\n actor TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE (reference, seq)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS admin_credit_adjustment_events_reference ON admin_credit_adjustment_events(reference)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_audit_jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER,\n status TEXT DEFAULT 'pending',\n client_domain TEXT,\n session_path TEXT,\n request TEXT,\n result TEXT,\n error TEXT,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_audit_phase_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n job_id TEXT REFERENCES site_audit_jobs(id),\n phase TEXT,\n completed_at TEXT DEFAULT (datetime('now')),\n output_summary TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_audit_jobs_user_id ON site_audit_jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_audit_jobs_status ON site_audit_jobs(status)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_audit_phase_log_job_id ON site_audit_phase_log(job_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_extract_jobs (\n id TEXT PRIMARY KEY,\n user_id INTEGER,\n status TEXT DEFAULT 'pending',\n start_url TEXT,\n options TEXT,\n total_urls INTEGER DEFAULT 0,\n done_urls INTEGER DEFAULT 0,\n attempted_urls INTEGER DEFAULT 0,\n successful_urls INTEGER DEFAULT 0,\n failed_urls INTEGER DEFAULT 0,\n idempotency_key TEXT,\n request_fingerprint TEXT,\n dispatch_attempts INTEGER DEFAULT 0,\n next_dispatch_at TEXT,\n dispatch_error TEXT,\n settlement_attempts INTEGER DEFAULT 0,\n next_settlement_at TEXT,\n settlement_error TEXT,\n artifacts TEXT,\n error TEXT,\n billed_mc INTEGER,\n created_at TEXT DEFAULT (datetime('now')),\n updated_at TEXT DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS site_extract_pages (\n job_id TEXT,\n url TEXT,\n page TEXT,\n PRIMARY KEY (job_id, url)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_jobs_user_id ON site_extract_jobs(user_id)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_jobs_status ON site_extract_jobs(status)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS site_extract_pages_job_id ON site_extract_pages(job_id)`)\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN attempted_urls INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN successful_urls INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN failed_urls INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN idempotency_key TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN request_fingerprint TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN dispatch_attempts INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN next_dispatch_at TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN dispatch_error TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN settlement_attempts INTEGER DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN next_settlement_at TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE site_extract_jobs ADD COLUMN settlement_error TEXT`) } catch {}\n await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS site_extract_jobs_idempotency ON site_extract_jobs(user_id, idempotency_key) WHERE idempotency_key IS NOT NULL`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS password_reset_tokens (\n token TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n expires_at TEXT NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS prt_user_id ON password_reset_tokens(user_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n scope TEXT NOT NULL,\n key_hash TEXT NOT NULL,\n window_start INTEGER NOT NULL,\n count INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (scope, key_hash, window_start)\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS request_events (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n source TEXT NOT NULL,\n status TEXT NOT NULL,\n query TEXT NOT NULL,\n location TEXT,\n result_count INTEGER,\n result TEXT,\n error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS request_events_user_created_at ON request_events(user_id, created_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_schedules (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n workflow_id TEXT NOT NULL,\n name TEXT NOT NULL,\n input_json TEXT NOT NULL,\n cadence TEXT NOT NULL,\n cron_expr TEXT,\n timezone TEXT NOT NULL DEFAULT 'UTC',\n webhook_url TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n next_run_at TEXT,\n last_run_at TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_schedules_user_status ON workflow_schedules(user_id, status, created_at DESC)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_schedules_due ON workflow_schedules(status, next_run_at)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_runs (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n schedule_id TEXT,\n workflow_id TEXT NOT NULL,\n input_json TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'queued',\n manifest_json TEXT,\n error_message TEXT,\n queued_at TEXT NOT NULL DEFAULT (datetime('now')),\n started_at TEXT,\n completed_at TEXT,\n idempotency_key TEXT,\n step_index INTEGER NOT NULL DEFAULT 0,\n total_steps INTEGER,\n step_state_json TEXT,\n step_log_json TEXT,\n webhook_url TEXT\n )\n `)\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN step_index INTEGER NOT NULL DEFAULT 0`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN total_steps INTEGER`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN step_state_json TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN step_log_json TEXT`) } catch {}\n try { await db.execute(`ALTER TABLE workflow_runs ADD COLUMN webhook_url TEXT`) } catch {}\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_runs_user_created ON workflow_runs(user_id, queued_at DESC)`)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_runs_schedule ON workflow_runs(schedule_id, queued_at DESC)`)\n await db.execute(`CREATE UNIQUE INDEX IF NOT EXISTS workflow_runs_idempotency ON workflow_runs(idempotency_key) WHERE idempotency_key IS NOT NULL`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_artifacts (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES workflow_runs(id),\n user_id INTEGER NOT NULL REFERENCES users(id),\n kind TEXT NOT NULL,\n label TEXT NOT NULL,\n path TEXT NOT NULL,\n content_type TEXT NOT NULL,\n bytes INTEGER,\n rows_count INTEGER,\n content TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n try { await db.execute(`ALTER TABLE workflow_artifacts ADD COLUMN content TEXT`) } catch {}\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_artifacts_run ON workflow_artifacts(run_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workflow_webhook_deliveries (\n id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL REFERENCES workflow_runs(id),\n schedule_id TEXT,\n user_id INTEGER NOT NULL REFERENCES users(id),\n webhook_url TEXT NOT NULL,\n status TEXT NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n delivered_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS workflow_webhook_deliveries_run ON workflow_webhook_deliveries(run_id)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS stripe_events (\n event_id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'processed',\n attempt_count INTEGER NOT NULL DEFAULT 1,\n claim_token TEXT,\n last_error TEXT,\n processed_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n for (const col of [\n `status TEXT NOT NULL DEFAULT 'processed'`,\n 'attempt_count INTEGER NOT NULL DEFAULT 1',\n 'claim_token TEXT',\n 'last_error TEXT',\n 'updated_at TEXT',\n ]) {\n try {\n await db.execute(`ALTER TABLE stripe_events ADD COLUMN ${col}`)\n } catch {\n void 0\n }\n }\n await db.execute(`\n UPDATE stripe_events\n SET updated_at = COALESCE(updated_at, processed_at),\n status = COALESCE(status, 'processed'),\n attempt_count = MAX(1, COALESCE(attempt_count, 1))\n WHERE updated_at IS NULL\n OR status IS NULL\n OR attempt_count IS NULL\n OR attempt_count < 1\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS oauth_clients (\n client_id TEXT PRIMARY KEY,\n redirect_uris TEXT NOT NULL,\n name TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS oauth_codes (\n code TEXT PRIMARY KEY,\n client_id TEXT NOT NULL,\n redirect_uri TEXT NOT NULL,\n code_challenge TEXT NOT NULL,\n identity TEXT NOT NULL,\n scope TEXT NOT NULL,\n resource TEXT,\n expires_at TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS oauth_tokens (\n refresh_token TEXT PRIMARY KEY,\n client_id TEXT NOT NULL,\n identity TEXT NOT NULL,\n scope TEXT NOT NULL,\n resource TEXT,\n expires_at TEXT NOT NULL,\n revoked INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL DEFAULT (datetime('now'))\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS oauth_tokens_identity ON oauth_tokens(identity)`)\n for (const col of ['replaced_by TEXT', 'rotated_at TEXT']) {\n try {\n await db.execute(`ALTER TABLE oauth_tokens ADD COLUMN ${col}`)\n } catch {\n void 0\n }\n }\n await db.execute(`\n CREATE TABLE IF NOT EXISTS page_snapshots (\n user_id INTEGER NOT NULL REFERENCES users(id),\n url TEXT NOT NULL,\n content_hash TEXT NOT NULL,\n content TEXT NOT NULL,\n title TEXT,\n content_bytes INTEGER NOT NULL,\n truncated INTEGER NOT NULL DEFAULT 0,\n checked_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (user_id, url)\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS page_snapshots_user_checked_at ON page_snapshots(user_id, checked_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS inbox_messages (\n id TEXT PRIMARY KEY,\n user_id INTEGER NOT NULL REFERENCES users(id),\n subject TEXT NOT NULL,\n body_md TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'operator',\n sent_at TEXT NOT NULL DEFAULT (datetime('now')),\n opened_at TEXT,\n deleted_at TEXT\n )\n `)\n await db.execute(`CREATE INDEX IF NOT EXISTS inbox_messages_user_sent_at ON inbox_messages(user_id, sent_at DESC)`)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS workshop_registrations (\n id TEXT PRIMARY KEY,\n event_slug TEXT NOT NULL,\n email TEXT NOT NULL,\n first_name TEXT NOT NULL,\n last_name TEXT,\n status TEXT NOT NULL,\n zoom_registrant_id TEXT,\n zoom_join_url TEXT,\n resend_contact_id TEXT,\n confirmation_email_id TEXT,\n field_notes_email_id TEXT,\n reminder_48h_email_id TEXT,\n reminder_24h_email_id TEXT,\n reminder_1h_email_id TEXT,\n last_error TEXT,\n registered_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n UNIQUE(event_slug, email)\n )\n `)\n for (const col of ['field_notes_email_id TEXT', 'reminder_48h_email_id TEXT', 'reminder_24h_email_id TEXT', 'reminder_1h_email_id TEXT']) {\n try {\n await db.execute(`ALTER TABLE workshop_registrations ADD COLUMN ${col}`)\n } catch {\n void 0\n }\n }\n await db.execute(`\n CREATE INDEX IF NOT EXISTS workshop_registrations_event_status\n ON workshop_registrations(event_slug, status, registered_at)\n `)\n await db.execute(`\n CREATE TABLE IF NOT EXISTS browser_profile_labels (\n user_id INTEGER NOT NULL REFERENCES users(id),\n profile TEXT NOT NULL,\n label TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT,\n PRIMARY KEY (user_id, profile)\n )\n `)\n if (!(await hasCurrentCoreSchema(db))) {\n throw new Error('core schema migration finished without all required tables, indexes, and columns')\n }\n await runIdempotentCoreDataBackfills(db)\n await db.execute({\n sql: 'INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)',\n args: [CORE_SCHEMA_VERSION],\n })\n}\n\nexport interface OAuthClient {\n client_id: string\n redirect_uris: string[]\n name: string | null\n created_at: string\n}\n\nexport async function registerClient(clientId: string, redirectUris: string[], name: string | null): Promise<void> {\n await getDb().execute({\n sql: 'INSERT INTO oauth_clients (client_id, redirect_uris, name) VALUES (?, ?, ?)',\n args: [clientId, JSON.stringify(redirectUris), name],\n })\n}\n\nexport async function getClient(clientId: string): Promise<OAuthClient | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM oauth_clients WHERE client_id = ?', args: [clientId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return undefined\n let uris: string[] = []\n try {\n const parsed = JSON.parse(String(row.redirect_uris))\n if (Array.isArray(parsed)) uris = parsed.filter((u): u is string => typeof u === 'string')\n } catch {\n uris = []\n }\n return {\n client_id: String(row.client_id),\n redirect_uris: uris,\n name: row.name != null ? String(row.name) : null,\n created_at: String(row.created_at),\n }\n}\n\nexport interface OAuthCode {\n code: string\n client_id: string\n redirect_uri: string\n code_challenge: string\n identity: string\n scope: string\n resource: string | null\n expires_at: string\n}\n\nexport async function putCode(code: OAuthCode): Promise<void> {\n await getDb().execute({\n sql: 'INSERT INTO oauth_codes (code, client_id, redirect_uri, code_challenge, identity, scope, resource, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',\n args: [code.code, code.client_id, code.redirect_uri, code.code_challenge, code.identity, code.scope, code.resource, code.expires_at],\n })\n}\n\nexport async function consumeCode(code: string): Promise<OAuthCode | undefined> {\n const db = getDb()\n const res = await db.execute({ sql: 'SELECT * FROM oauth_codes WHERE code = ?', args: [code] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n await db.execute({ sql: 'DELETE FROM oauth_codes WHERE code = ?', args: [code] })\n if (!row) return undefined\n return {\n code: String(row.code),\n client_id: String(row.client_id),\n redirect_uri: String(row.redirect_uri),\n code_challenge: String(row.code_challenge),\n identity: String(row.identity),\n scope: String(row.scope),\n resource: row.resource != null ? String(row.resource) : null,\n expires_at: String(row.expires_at),\n }\n}\n\nexport interface OAuthRefresh {\n refresh_token: string\n client_id: string\n identity: string\n scope: string\n resource: string | null\n expires_at: string\n revoked: number\n replaced_by?: string | null\n rotated_at?: string | null\n}\n\nexport async function putRefresh(token: OAuthRefresh): Promise<void> {\n await getDb().execute({\n sql: 'INSERT INTO oauth_tokens (refresh_token, client_id, identity, scope, resource, expires_at, revoked) VALUES (?, ?, ?, ?, ?, ?, 0)',\n args: [token.refresh_token, token.client_id, token.identity, token.scope, token.resource, token.expires_at],\n })\n}\n\nexport async function getRefresh(refreshToken: string): Promise<OAuthRefresh | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM oauth_tokens WHERE refresh_token = ?', args: [refreshToken] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return undefined\n return {\n refresh_token: String(row.refresh_token),\n client_id: String(row.client_id),\n identity: String(row.identity),\n scope: String(row.scope),\n resource: row.resource != null ? String(row.resource) : null,\n expires_at: String(row.expires_at),\n revoked: Number(row.revoked ?? 0),\n replaced_by: row.replaced_by != null ? String(row.replaced_by) : null,\n rotated_at: row.rotated_at != null ? String(row.rotated_at) : null,\n }\n}\n\nexport async function revokeRefresh(refreshToken: string): Promise<void> {\n await getDb().execute({ sql: 'UPDATE oauth_tokens SET revoked = 1 WHERE refresh_token = ?', args: [refreshToken] })\n}\n\nexport async function rotateRefresh(oldToken: string, next: OAuthRefresh): Promise<void> {\n await putRefresh(next)\n await getDb().execute({\n sql: 'UPDATE oauth_tokens SET revoked = 1, replaced_by = ?, rotated_at = ? WHERE refresh_token = ?',\n args: [next.refresh_token, new Date().toISOString(), oldToken],\n })\n}\n\nexport type StripeEventClaim =\n | { state: 'claimed'; token: string }\n | { state: 'processed' | 'processing' }\n\n/**\n * Claims one Stripe event for processing.\n *\n * Failed work is immediately retryable. A processing claim can be taken over\n * only after ten minutes, beyond the serverless request ceiling, and the token\n * prevents an older worker from completing or failing a newer claim.\n */\nexport async function claimStripeEvent(eventId: string): Promise<StripeEventClaim> {\n const token = randomUUID()\n const claimed = await getDb().execute({\n sql: `INSERT INTO stripe_events\n (event_id, status, attempt_count, claim_token, last_error, processed_at, updated_at)\n VALUES (?, 'processing', 1, ?, NULL, datetime('now'), datetime('now'))\n ON CONFLICT(event_id) DO UPDATE SET\n status = 'processing',\n attempt_count = stripe_events.attempt_count + 1,\n claim_token = excluded.claim_token,\n last_error = NULL,\n updated_at = datetime('now')\n WHERE stripe_events.status = 'failed'\n OR (\n stripe_events.status = 'processing'\n AND stripe_events.updated_at <= datetime('now', '-10 minutes')\n )`,\n args: [eventId, token],\n })\n if (claimed.rowsAffected > 0) return { state: 'claimed', token }\n\n const current = await getDb().execute({\n sql: 'SELECT status FROM stripe_events WHERE event_id = ? LIMIT 1',\n args: [eventId],\n })\n return { state: String(current.rows[0]?.status) === 'processed' ? 'processed' : 'processing' }\n}\n\nexport async function completeStripeEvent(eventId: string, token: string): Promise<boolean> {\n const result = await getDb().execute({\n sql: `UPDATE stripe_events\n SET status = 'processed',\n claim_token = NULL,\n last_error = NULL,\n processed_at = datetime('now'),\n updated_at = datetime('now')\n WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,\n args: [eventId, token],\n })\n return result.rowsAffected === 1\n}\n\nexport async function failStripeEvent(eventId: string, token: string, error: string): Promise<boolean> {\n const result = await getDb().execute({\n sql: `UPDATE stripe_events\n SET status = 'failed',\n claim_token = NULL,\n last_error = ?,\n updated_at = datetime('now')\n WHERE event_id = ? AND status = 'processing' AND claim_token = ?`,\n args: [error.slice(0, 500), eventId, token],\n })\n return result.rowsAffected === 1\n}\n\nexport async function stripeEventAlreadyProcessed(eventId: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: `SELECT 1 FROM stripe_events WHERE event_id = ? AND status = 'processed' LIMIT 1`,\n args: [eventId],\n })\n return res.rows.length > 0\n}\n\nexport function generateApiKey(): string {\n return 'sk_' + randomBytes(24).toString('hex')\n}\n\nexport interface User {\n id: number\n email: string\n name: string | null\n api_key: string\n key_active: number\n password_hash: string | null\n created_at: string\n active: number\n stripe_customer_id: string | null\n balance_mc: number\n extra_concurrency_slots: number\n concurrency_stripe_sub_id: string | null\n subscription_concurrency: number\n subscription_tier: string | null\n subscription_id: string | null\n memory_plan: string\n memory_subscription_id: string | null\n memory_provisioned: number\n}\n\nexport function hashPassword(password: string): string {\n const salt = randomBytes(16).toString('hex')\n const hash = scryptSync(password, salt, 64)\n return `${salt}:${hash.toString('hex')}`\n}\n\nexport function verifyPassword(password: string, stored: string): boolean {\n const [salt, hash] = stored.split(':')\n const hashBuf = Buffer.from(hash, 'hex')\n const derived = scryptSync(password, salt, 64)\n return timingSafeEqual(hashBuf, derived)\n}\n\nfunction rowToUser(row: Record<string, unknown>): User {\n return {\n id: Number(row.id),\n email: String(row.email),\n name: row.name != null ? String(row.name) : null,\n api_key: String(row.api_key),\n key_active: Number(row.key_active ?? 1),\n password_hash: row.password_hash != null ? String(row.password_hash) : null,\n created_at: String(row.created_at),\n active: Number(row.active),\n stripe_customer_id: row.stripe_customer_id != null ? String(row.stripe_customer_id) : null,\n balance_mc: Number(row.balance_mc ?? 0),\n extra_concurrency_slots: Number(row.extra_concurrency_slots ?? 0),\n concurrency_stripe_sub_id: row.concurrency_stripe_sub_id != null ? String(row.concurrency_stripe_sub_id) : null,\n subscription_concurrency: Number(row.subscription_concurrency ?? 0),\n subscription_tier: row.subscription_tier != null ? String(row.subscription_tier) : null,\n subscription_id: row.subscription_id != null ? String(row.subscription_id) : null,\n memory_plan: row.memory_plan != null ? String(row.memory_plan) : 'free',\n memory_subscription_id: row.memory_subscription_id != null ? String(row.memory_subscription_id) : null,\n memory_provisioned: Number(row.memory_provisioned ?? 0),\n }\n}\n\nexport async function getUserByEmail(email: string): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE email = ? AND active = 1', args: [email] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function getUserByApiKey(api_key: string): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE api_key = ? AND active = 1 AND key_active = 1', args: [api_key] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function getUserById(id: number | bigint): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE id = ? AND active = 1', args: [id] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function getUserMemoryCreds(id: number | bigint): Promise<{ memory_key: string | null; memory_identity: string | null }> {\n const res = await getDb().execute({ sql: 'SELECT memory_key, memory_identity FROM users WHERE id = ?', args: [id] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return {\n memory_key: row?.memory_key != null ? String(row.memory_key) : null,\n memory_identity: row?.memory_identity != null ? String(row.memory_identity) : null,\n }\n}\n\nexport async function setUserMemoryCreds(id: number | bigint, memoryKeyEncrypted: string, memoryIdentity: string): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET memory_key = ?, memory_identity = ? WHERE id = ?', args: [memoryKeyEncrypted, memoryIdentity, id] })\n}\n\nexport async function setPassword(id: number | bigint, password: string): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET password_hash = ? WHERE id = ?', args: [hashPassword(password), id] })\n}\n\nexport async function getUserStats(userId: number | bigint): Promise<{ total: number; done: number; failed: number; totalQuestions: number; lastUsed: string | null }> {\n const db = getDb()\n const [totR, doneR, failR, tqR, luR] = await Promise.all([\n db.execute({ sql: 'SELECT COUNT(*) as n FROM jobs WHERE user_id = ?', args: [userId] }),\n db.execute({ sql: \"SELECT COUNT(*) as n FROM jobs WHERE user_id = ? AND status = 'done'\", args: [userId] }),\n db.execute({ sql: \"SELECT COUNT(*) as n FROM jobs WHERE user_id = ? AND status = 'failed'\", args: [userId] }),\n db.execute({ sql: \"SELECT COALESCE(SUM(json_extract(result, '$.totalQuestions')), 0) as n FROM jobs WHERE user_id = ? AND status = 'done'\", args: [userId] }),\n db.execute({ sql: 'SELECT MAX(created_at) as t FROM jobs WHERE user_id = ?', args: [userId] }),\n ])\n return {\n total: Number(totR.rows[0]?.n ?? 0),\n done: Number(doneR.rows[0]?.n ?? 0),\n failed: Number(failR.rows[0]?.n ?? 0),\n totalQuestions: Number(tqR.rows[0]?.n ?? 0),\n lastUsed: luR.rows[0]?.t != null ? String(luR.rows[0].t) : null,\n }\n}\n\nexport async function createUser(email: string, name?: string, password?: string, stripeCustomerId?: string): Promise<{ id: number | bigint; email: string; name?: string; api_key: string; password?: string }> {\n const db = getDb()\n const api_key = generateApiKey()\n const plainPassword = password ?? randomBytes(6).toString('hex')\n const password_hash = hashPassword(plainPassword)\n const result = await db.execute({\n sql: 'INSERT INTO users (email, name, api_key, password_hash, stripe_customer_id) VALUES (?, ?, ?, ?, ?)',\n args: [email, name ?? null, api_key, password_hash, stripeCustomerId ?? null],\n })\n return { id: result.lastInsertRowid!, email, name, api_key, password: plainPassword }\n}\n\nexport async function checkRateLimit(\n scope: string,\n key: string,\n limit: number,\n windowSeconds: number,\n): Promise<{ allowed: boolean; count: number; remaining: number; resetSeconds: number }> {\n const nowSeconds = Math.floor(Date.now() / 1000)\n const windowStart = Math.floor(nowSeconds / windowSeconds) * windowSeconds\n const keyHash = createHash('sha256').update(key).digest('hex')\n const db = getDb()\n\n if (!_rateLimitSchemaReady) {\n await db.execute(`\n CREATE TABLE IF NOT EXISTS rate_limits (\n scope TEXT NOT NULL,\n key_hash TEXT NOT NULL,\n window_start INTEGER NOT NULL,\n count INTEGER NOT NULL DEFAULT 0,\n updated_at TEXT NOT NULL DEFAULT (datetime('now')),\n PRIMARY KEY (scope, key_hash, window_start)\n )\n `)\n _rateLimitSchemaReady = true\n }\n\n await db.execute({\n sql: `\n INSERT INTO rate_limits (scope, key_hash, window_start, count)\n VALUES (?, ?, ?, 1)\n ON CONFLICT(scope, key_hash, window_start)\n DO UPDATE SET count = count + 1, updated_at = datetime('now')\n `,\n args: [scope, keyHash, windowStart],\n })\n\n const res = await db.execute({\n sql: 'SELECT count FROM rate_limits WHERE scope = ? AND key_hash = ? AND window_start = ?',\n args: [scope, keyHash, windowStart],\n })\n const count = Number(res.rows[0]?.count ?? 1)\n return {\n allowed: count <= limit,\n count,\n remaining: Math.max(0, limit - count),\n resetSeconds: Math.max(1, windowStart + windowSeconds - nowSeconds),\n }\n}\n\nexport async function createPasswordResetToken(userId: number | bigint): Promise<string> {\n const token = randomBytes(32).toString('hex')\n const tokenHash = createHash('sha256').update(token).digest('hex')\n const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString()\n await getDb().execute({\n sql: 'INSERT INTO password_reset_tokens (token, user_id, expires_at) VALUES (?, ?, ?)',\n args: [tokenHash, userId, expiresAt],\n })\n return token\n}\n\nexport async function consumePasswordResetToken(token: string): Promise<number | null> {\n const db = getDb()\n const tokenHash = createHash('sha256').update(token).digest('hex')\n const res = await db.execute({\n sql: \"SELECT user_id, expires_at, used FROM password_reset_tokens WHERE token = ?\",\n args: [tokenHash],\n })\n const row = res.rows[0]\n if (!row) return null\n if (Number(row.used)) return null\n if (new Date(String(row.expires_at)) < new Date()) return null\n await db.execute({ sql: 'UPDATE password_reset_tokens SET used = 1 WHERE token = ?', args: [tokenHash] })\n return Number(row.user_id)\n}\n\nexport async function rotateApiKey(userId: number | bigint): Promise<string> {\n const newKey = generateApiKey()\n await getDb().execute({ sql: 'UPDATE users SET api_key = ?, key_active = 1 WHERE id = ?', args: [newKey, userId] })\n return newKey\n}\n\nexport async function revokeApiKey(userId: number | bigint): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET key_active = 0 WHERE id = ?', args: [userId] })\n}\n\nexport async function listUsers(): Promise<Omit<User, 'api_key'>[]> {\n const res = await getDb().execute('SELECT id, email, name, created_at, active FROM users ORDER BY created_at DESC')\n return res.rows.map(r => rowToUser({ ...r as unknown as Record<string, unknown>, api_key: '', key_active: 1 }))\n}\n\nexport async function deactivateUser(id: number): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET active = 0 WHERE id = ?', args: [id] })\n}\n\ninterface RawJob {\n id: string\n user_id: number\n status: string\n query: string\n options: string\n callback_url: string | null\n result: string | null\n error: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n}\n\nexport interface Job {\n id: string\n user_id: number\n status: string\n query: string\n options: Record<string, unknown>\n callback_url: string | null\n result: unknown | null\n error: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n}\n\nexport interface HarvestAttemptLog {\n id: string\n job_id: string\n user_id: number\n attempt_number: number\n max_attempts: number\n query: string\n location: string | null\n max_questions: number\n status: string\n outcome: string | null\n kernel_session_id: string | null\n question_count: number | null\n duration_ms: number | null\n error: string | null\n will_retry: boolean | null\n kernel_delete_started: boolean\n kernel_delete_succeeded: boolean | null\n kernel_delete_error: string | null\n browser_close_succeeded: boolean | null\n browser_close_error: string | null\n debug: unknown | null\n created_at: string\n completed_at: string | null\n}\n\nexport interface StartHarvestAttemptInput {\n jobId: string\n userId: number | bigint\n attemptNumber: number\n maxAttempts: number\n query: string\n location: string | null\n maxQuestions: number\n startedAt: string\n}\n\nexport interface FinishHarvestAttemptInput {\n jobId: string\n attemptNumber: number\n outcome: string\n kernelSessionId: string | null\n questionCount: number\n durationMs: number\n error: string | null\n willRetry: boolean\n kernelDeleteStarted: boolean\n kernelDeleteSucceeded: boolean | null\n kernelDeleteError: string | null\n browserCloseSucceeded: boolean | null\n browserCloseError: string | null\n debug: unknown | null\n completedAt: string\n}\n\nfunction rowToRawJob(row: Record<string, unknown>): RawJob {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n status: String(row.status),\n query: String(row.query),\n options: String(row.options),\n callback_url: row.callback_url != null ? String(row.callback_url) : null,\n result: row.result != null ? String(row.result) : null,\n error: row.error != null ? String(row.error) : null,\n created_at: String(row.created_at),\n started_at: row.started_at != null ? String(row.started_at) : null,\n completed_at: row.completed_at != null ? String(row.completed_at) : null,\n }\n}\n\nfunction nullableBoolean(value: unknown): boolean | null {\n return value == null ? null : Number(value) === 1\n}\n\nfunction parseNullableJson(value: unknown): unknown | null {\n if (value == null) return null\n try {\n return JSON.parse(String(value)) as unknown\n } catch {\n return null\n }\n}\n\nfunction rowToHarvestAttemptLog(row: Record<string, unknown>): HarvestAttemptLog {\n return {\n id: String(row.id),\n job_id: String(row.job_id),\n user_id: Number(row.user_id),\n attempt_number: Number(row.attempt_number),\n max_attempts: Number(row.max_attempts),\n query: String(row.query),\n location: row.location != null ? String(row.location) : null,\n max_questions: Number(row.max_questions),\n status: String(row.status),\n outcome: row.outcome != null ? String(row.outcome) : null,\n kernel_session_id: row.kernel_session_id != null ? String(row.kernel_session_id) : null,\n question_count: row.question_count != null ? Number(row.question_count) : null,\n duration_ms: row.duration_ms != null ? Number(row.duration_ms) : null,\n error: row.error != null ? String(row.error) : null,\n will_retry: nullableBoolean(row.will_retry),\n kernel_delete_started: Number(row.kernel_delete_started ?? 0) === 1,\n kernel_delete_succeeded: nullableBoolean(row.kernel_delete_succeeded),\n kernel_delete_error: row.kernel_delete_error != null ? String(row.kernel_delete_error) : null,\n browser_close_succeeded: nullableBoolean(row.browser_close_succeeded),\n browser_close_error: row.browser_close_error != null ? String(row.browser_close_error) : null,\n debug: parseNullableJson(row.debug_json),\n created_at: String(row.created_at),\n completed_at: row.completed_at != null ? String(row.completed_at) : null,\n }\n}\n\nfunction deserialize(raw: RawJob): Job {\n return {\n ...raw,\n options: JSON.parse(raw.options) as Record<string, unknown>,\n result: raw.result ? JSON.parse(raw.result) : null,\n }\n}\n\nexport async function createJob(userId: number | bigint, query: string, options: object, callbackUrl?: string): Promise<string> {\n const id = randomUUID()\n await createJobWithId(id, userId, query, options, callbackUrl)\n return id\n}\n\nexport async function createJobWithId(id: string, userId: number | bigint, query: string, options: object, callbackUrl?: string): Promise<void> {\n if (!/^[a-zA-Z0-9-]{1,100}$/.test(id)) throw new Error('job id is invalid')\n await getDb().execute({\n sql: 'INSERT INTO jobs (id, user_id, query, options, callback_url) VALUES (?, ?, ?, ?, ?)',\n args: [id, userId, query, JSON.stringify(options), callbackUrl ?? null],\n })\n}\n\nexport async function createRunningJob(userId: number | bigint, query: string, options: object): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: `INSERT INTO jobs (id, user_id, query, options, status, started_at) VALUES (?, ?, ?, ?, 'running', datetime('now'))`,\n args: [id, userId, query, JSON.stringify(options)],\n })\n return id\n}\n\nexport async function startHarvestAttempt(input: StartHarvestAttemptInput): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO harvest_attempts (\n id, job_id, user_id, attempt_number, max_attempts, query, location, max_questions, status, created_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'running', ?)\n ON CONFLICT(job_id, attempt_number) DO UPDATE SET\n status = 'running',\n created_at = excluded.created_at,\n completed_at = NULL,\n outcome = NULL,\n error = NULL,\n debug_json = NULL\n `,\n args: [\n randomUUID(),\n input.jobId,\n input.userId,\n input.attemptNumber,\n input.maxAttempts,\n input.query,\n input.location,\n input.maxQuestions,\n input.startedAt,\n ],\n })\n}\n\nexport async function finishHarvestAttempt(input: FinishHarvestAttemptInput): Promise<void> {\n await getDb().execute({\n sql: `\n UPDATE harvest_attempts SET\n status = 'finished',\n outcome = ?,\n kernel_session_id = ?,\n question_count = ?,\n duration_ms = ?,\n error = ?,\n will_retry = ?,\n kernel_delete_started = ?,\n kernel_delete_succeeded = ?,\n kernel_delete_error = ?,\n browser_close_succeeded = ?,\n browser_close_error = ?,\n debug_json = ?,\n completed_at = ?\n WHERE job_id = ? AND attempt_number = ?\n `,\n args: [\n input.outcome,\n input.kernelSessionId,\n input.questionCount,\n input.durationMs,\n input.error,\n input.willRetry ? 1 : 0,\n input.kernelDeleteStarted ? 1 : 0,\n input.kernelDeleteSucceeded == null ? null : input.kernelDeleteSucceeded ? 1 : 0,\n input.kernelDeleteError,\n input.browserCloseSucceeded == null ? null : input.browserCloseSucceeded ? 1 : 0,\n input.browserCloseError,\n input.debug == null ? null : JSON.stringify(input.debug),\n input.completedAt,\n input.jobId,\n input.attemptNumber,\n ],\n })\n}\n\nexport async function listHarvestAttempts(jobId: string, userId?: number | bigint): Promise<HarvestAttemptLog[]> {\n const res = userId == null\n ? await getDb().execute({\n sql: `SELECT * FROM harvest_attempts WHERE job_id = ? ORDER BY attempt_number ASC`,\n args: [jobId],\n })\n : await getDb().execute({\n sql: `\n SELECT ha.*\n FROM harvest_attempts ha\n INNER JOIN jobs j ON j.id = ha.job_id\n WHERE ha.job_id = ? AND j.user_id = ?\n ORDER BY ha.attempt_number ASC\n `,\n args: [jobId, userId],\n })\n return res.rows.map(r => rowToHarvestAttemptLog(r as unknown as Record<string, unknown>))\n}\n\nexport async function getJob(id: string, userId?: number | bigint): Promise<Job | undefined> {\n const db = getDb()\n const res = userId\n ? await db.execute({ sql: 'SELECT * FROM jobs WHERE id = ? AND user_id = ?', args: [id, userId] })\n : await db.execute({ sql: 'SELECT * FROM jobs WHERE id = ?', args: [id] })\n return res.rows[0] ? deserialize(rowToRawJob(res.rows[0] as unknown as Record<string, unknown>)) : undefined\n}\n\nexport async function listJobs(userId: number | bigint): Promise<Job[]> {\n const res = await getDb().execute({ sql: 'SELECT * FROM jobs WHERE user_id = ? ORDER BY created_at DESC LIMIT 50', args: [userId] })\n return res.rows.map(r => deserialize(rowToRawJob(r as unknown as Record<string, unknown>)))\n}\n\nexport interface PageSnapshotRow {\n contentHash: string\n content: string\n title: string | null\n contentBytes: number\n truncated: boolean\n checkedAt: string\n}\n\nexport async function getPageSnapshot(userId: number | bigint, url: string): Promise<PageSnapshotRow | null> {\n const res = await getDb().execute({\n sql: 'SELECT content_hash, content, title, content_bytes, truncated, checked_at FROM page_snapshots WHERE user_id = ? AND url = ?',\n args: [userId, url],\n })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return null\n return {\n contentHash: String(row.content_hash),\n content: String(row.content),\n title: row.title == null ? null : String(row.title),\n contentBytes: Number(row.content_bytes),\n truncated: Number(row.truncated) === 1,\n checkedAt: String(row.checked_at),\n }\n}\n\nexport interface UpsertPageSnapshotInput {\n contentHash: string\n content: string\n title: string | null\n contentBytes: number\n truncated: boolean\n}\n\nexport async function upsertPageSnapshot(userId: number | bigint, url: string, snapshot: UpsertPageSnapshotInput): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO page_snapshots (user_id, url, content_hash, content, title, content_bytes, truncated, checked_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(user_id, url) DO UPDATE SET\n content_hash = excluded.content_hash,\n content = excluded.content,\n title = excluded.title,\n content_bytes = excluded.content_bytes,\n truncated = excluded.truncated,\n checked_at = excluded.checked_at\n `,\n args: [userId, url, snapshot.contentHash, snapshot.content, snapshot.title, snapshot.contentBytes, snapshot.truncated ? 1 : 0],\n })\n}\n\nexport interface RequestEvent {\n id: string\n user_id: number\n source: string\n status: string\n query: string\n location: string | null\n result_count: number | null\n result: unknown | null\n error: string | null\n created_at: string\n}\n\nconst REQUEST_EVENT_RESULT_MAX_BYTES = 32 * 1024\n\nfunction serializeRequestEventResult(result: unknown): string | null {\n if (result == null) return null\n const serialized = JSON.stringify(result)\n const bytes = Buffer.byteLength(serialized, 'utf8')\n if (bytes <= REQUEST_EVENT_RESULT_MAX_BYTES) return serialized\n\n // request_events powers the lightweight history list; the full response is\n // already returned to the caller (and, for jobs/exports, stored in its\n // canonical artifact). Keeping another multi-megabyte copy here grew the\n // production database by gigabytes without any product consumer reading it.\n return JSON.stringify({\n truncated: true,\n originalBytes: bytes,\n sha256: createHash('sha256').update(serialized).digest('hex'),\n })\n}\n\nfunction rowToRequestEvent(row: Record<string, unknown>): RequestEvent {\n const rawResult = row.result != null ? String(row.result) : null\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n source: String(row.source),\n status: String(row.status),\n query: String(row.query),\n location: row.location != null ? String(row.location) : null,\n result_count: row.result_count != null ? Number(row.result_count) : null,\n result: rawResult ? JSON.parse(rawResult) : null,\n error: row.error != null ? String(row.error) : null,\n created_at: String(row.created_at),\n }\n}\n\nexport async function logRequestEvent(input: {\n userId: number | bigint\n source: string\n status: 'done' | 'failed'\n query: string\n location?: string | null\n resultCount?: number | null\n result?: unknown\n error?: string | null\n}): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: `\n INSERT INTO request_events (id, user_id, source, status, query, location, result_count, result, error)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n id,\n input.userId,\n input.source,\n input.status,\n input.query,\n input.location ?? null,\n input.resultCount ?? null,\n serializeRequestEventResult(input.result),\n input.error ?? null,\n ],\n })\n return id\n}\n\nexport async function listRequestEvents(userId: number | bigint, limit = 100): Promise<RequestEvent[]> {\n const res = await getDb().execute({\n sql: `\n SELECT id, user_id, source, status, query, location, result_count, NULL AS result, error, created_at\n FROM request_events\n WHERE user_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n `,\n args: [userId, limit],\n })\n return res.rows.map(r => rowToRequestEvent(r as unknown as Record<string, unknown>))\n}\n\nexport type WorkflowScheduleStatus = 'active' | 'paused' | 'deleted'\nexport type WorkflowCadence = 'daily' | 'weekly' | 'monthly'\nexport type WorkflowRunStatus = 'queued' | 'running' | 'succeeded' | 'partial' | 'failed' | 'cancelled'\n\nexport interface WorkflowScheduleRecord {\n id: string\n user_id: number\n workflow_id: string\n name: string\n input: Record<string, unknown>\n cadence: WorkflowCadence\n cron_expr: string | null\n timezone: string\n webhook_url: string | null\n status: WorkflowScheduleStatus\n next_run_at: string | null\n last_run_at: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface WorkflowRunRecord {\n id: string\n user_id: number\n schedule_id: string | null\n workflow_id: string\n input: Record<string, unknown>\n status: WorkflowRunStatus\n manifest: unknown | null\n error_message: string | null\n queued_at: string\n started_at: string | null\n completed_at: string | null\n idempotency_key: string | null\n step_index: number\n total_steps: number | null\n step_log: WorkflowStepLogEntry[]\n webhook_url: string | null\n}\n\nexport interface WorkflowStepLogEntry {\n stepIndex: number\n stepId: string\n stepTitle: string\n output: Record<string, unknown>\n warnings: string[]\n at: string\n}\n\nexport interface WorkflowArtifactRecord {\n id: string\n run_id: string\n user_id: number\n kind: string\n label: string\n path: string\n content_type: string\n bytes: number | null\n rows_count: number | null\n content: string | null\n created_at: string\n}\n\nfunction parseJsonRecordValue(value: unknown): Record<string, unknown> {\n if (value == null) return {}\n try {\n const parsed = JSON.parse(String(value)) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {}\n } catch {\n return {}\n }\n}\n\nfunction rowToWorkflowSchedule(row: Record<string, unknown>): WorkflowScheduleRecord {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n workflow_id: String(row.workflow_id),\n name: String(row.name),\n input: parseJsonRecordValue(row.input_json),\n cadence: String(row.cadence) as WorkflowCadence,\n cron_expr: row.cron_expr != null ? String(row.cron_expr) : null,\n timezone: String(row.timezone ?? 'UTC'),\n webhook_url: row.webhook_url != null ? String(row.webhook_url) : null,\n status: String(row.status) as WorkflowScheduleStatus,\n next_run_at: row.next_run_at != null ? String(row.next_run_at) : null,\n last_run_at: row.last_run_at != null ? String(row.last_run_at) : null,\n created_at: String(row.created_at),\n updated_at: String(row.updated_at),\n }\n}\n\nfunction rowToWorkflowRun(row: Record<string, unknown>): WorkflowRunRecord {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n schedule_id: row.schedule_id != null ? String(row.schedule_id) : null,\n workflow_id: String(row.workflow_id),\n input: parseJsonRecordValue(row.input_json),\n status: String(row.status) as WorkflowRunStatus,\n manifest: row.manifest_json != null ? parseNullableJson(row.manifest_json) : null,\n error_message: row.error_message != null ? String(row.error_message) : null,\n queued_at: String(row.queued_at),\n started_at: row.started_at != null ? String(row.started_at) : null,\n completed_at: row.completed_at != null ? String(row.completed_at) : null,\n idempotency_key: row.idempotency_key != null ? String(row.idempotency_key) : null,\n step_index: row.step_index != null ? Number(row.step_index) : 0,\n total_steps: row.total_steps != null ? Number(row.total_steps) : null,\n step_log: parseWorkflowStepLog(row.step_log_json),\n webhook_url: row.webhook_url != null ? String(row.webhook_url) : null,\n }\n}\n\nfunction parseWorkflowStepLog(value: unknown): WorkflowStepLogEntry[] {\n if (value == null) return []\n try {\n const parsed = JSON.parse(String(value)) as unknown\n return Array.isArray(parsed) ? parsed as WorkflowStepLogEntry[] : []\n } catch {\n return []\n }\n}\n\nfunction rowToWorkflowArtifact(row: Record<string, unknown>): WorkflowArtifactRecord {\n return {\n id: String(row.id),\n run_id: String(row.run_id),\n user_id: Number(row.user_id),\n kind: String(row.kind),\n label: String(row.label),\n path: String(row.path),\n content_type: String(row.content_type),\n bytes: row.bytes != null ? Number(row.bytes) : null,\n rows_count: row.rows_count != null ? Number(row.rows_count) : null,\n content: row.content != null ? String(row.content) : null,\n created_at: String(row.created_at),\n }\n}\n\nexport async function createWorkflowSchedule(input: {\n userId: number | bigint\n workflowId: string\n name: string\n workflowInput: Record<string, unknown>\n cadence: WorkflowCadence\n timezone?: string\n webhookUrl?: string | null\n nextRunAt?: string | null\n}): Promise<WorkflowScheduleRecord> {\n const id = randomUUID()\n await getDb().execute({\n sql: `\n INSERT INTO workflow_schedules (\n id, user_id, workflow_id, name, input_json, cadence, timezone, webhook_url, next_run_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n id,\n input.userId,\n input.workflowId,\n input.name,\n JSON.stringify(input.workflowInput),\n input.cadence,\n input.timezone ?? 'UTC',\n input.webhookUrl ?? null,\n input.nextRunAt ?? null,\n ],\n })\n const schedule = await getWorkflowSchedule(id, input.userId)\n if (!schedule) throw new Error('Failed to create workflow schedule')\n return schedule\n}\n\nexport async function getWorkflowSchedule(id: string, userId?: number | bigint): Promise<WorkflowScheduleRecord | null> {\n const res = userId == null\n ? await getDb().execute({ sql: 'SELECT * FROM workflow_schedules WHERE id = ? AND status != ? LIMIT 1', args: [id, 'deleted'] })\n : await getDb().execute({ sql: 'SELECT * FROM workflow_schedules WHERE id = ? AND user_id = ? AND status != ? LIMIT 1', args: [id, userId, 'deleted'] })\n return res.rows[0] ? rowToWorkflowSchedule(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function listWorkflowSchedules(userId: number | bigint): Promise<WorkflowScheduleRecord[]> {\n const res = await getDb().execute({\n sql: `SELECT * FROM workflow_schedules WHERE user_id = ? AND status != 'deleted' ORDER BY created_at DESC LIMIT 100`,\n args: [userId],\n })\n return res.rows.map(r => rowToWorkflowSchedule(r as unknown as Record<string, unknown>))\n}\n\nexport async function patchWorkflowSchedule(input: {\n id: string\n userId: number | bigint\n name?: string\n status?: WorkflowScheduleStatus\n workflowInput?: Record<string, unknown>\n cadence?: WorkflowCadence\n timezone?: string\n webhookUrl?: string | null\n nextRunAt?: string | null\n}): Promise<WorkflowScheduleRecord | null> {\n const sets: string[] = []\n const args: Array<string | number | bigint | null> = []\n if (input.name !== undefined) { sets.push('name = ?'); args.push(input.name) }\n if (input.status !== undefined) { sets.push('status = ?'); args.push(input.status) }\n if (input.workflowInput !== undefined) { sets.push('input_json = ?'); args.push(JSON.stringify(input.workflowInput)) }\n if (input.cadence !== undefined) { sets.push('cadence = ?'); args.push(input.cadence) }\n if (input.timezone !== undefined) { sets.push('timezone = ?'); args.push(input.timezone) }\n if (input.webhookUrl !== undefined) { sets.push('webhook_url = ?'); args.push(input.webhookUrl) }\n if (input.nextRunAt !== undefined) { sets.push('next_run_at = ?'); args.push(input.nextRunAt) }\n if (sets.length === 0) return getWorkflowSchedule(input.id, input.userId)\n sets.push(`updated_at = datetime('now')`)\n await getDb().execute({\n sql: `UPDATE workflow_schedules SET ${sets.join(', ')} WHERE id = ? AND user_id = ? AND status != 'deleted'`,\n args: [...args, input.id, input.userId],\n })\n return getWorkflowSchedule(input.id, input.userId)\n}\n\nexport async function deleteWorkflowSchedule(id: string, userId: number | bigint): Promise<boolean> {\n const res = await getDb().execute({\n sql: `UPDATE workflow_schedules SET status = 'deleted', updated_at = datetime('now') WHERE id = ? AND user_id = ? AND status != 'deleted'`,\n args: [id, userId],\n })\n return res.rowsAffected > 0\n}\n\nexport async function listDueWorkflowSchedules(nowIso: string, limit = 10): Promise<WorkflowScheduleRecord[]> {\n const res = await getDb().execute({\n sql: `\n SELECT * FROM workflow_schedules\n WHERE status = 'active' AND next_run_at IS NOT NULL AND next_run_at <= ?\n ORDER BY next_run_at ASC\n LIMIT ?\n `,\n args: [nowIso, limit],\n })\n return res.rows.map(r => rowToWorkflowSchedule(r as unknown as Record<string, unknown>))\n}\n\nexport async function markWorkflowScheduleRan(id: string, lastRunAt: string, nextRunAt: string | null): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_schedules SET last_run_at = ?, next_run_at = ?, updated_at = datetime('now') WHERE id = ?`,\n args: [lastRunAt, nextRunAt, id],\n })\n}\n\nexport async function createWorkflowRun(input: {\n id?: string\n userId: number | bigint\n workflowId: string\n workflowInput: Record<string, unknown>\n scheduleId?: string | null\n idempotencyKey?: string | null\n webhookUrl?: string | null\n totalSteps?: number | null\n}): Promise<WorkflowRunRecord> {\n const id = input.id ?? randomUUID()\n const args = [\n id,\n input.userId,\n input.scheduleId ?? null,\n input.workflowId,\n JSON.stringify(input.workflowInput),\n input.idempotencyKey ?? null,\n input.webhookUrl ?? null,\n input.totalSteps ?? null,\n ]\n try {\n await getDb().execute({\n sql: `\n INSERT INTO workflow_runs (id, user_id, schedule_id, workflow_id, input_json, idempotency_key, webhook_url, total_steps)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args,\n })\n } catch (err) {\n if (input.idempotencyKey) {\n const existing = await getWorkflowRunByIdempotencyKey(input.idempotencyKey)\n if (existing) return existing\n }\n throw err\n }\n const run = await getWorkflowRun(id, input.userId)\n if (!run) throw new Error('Failed to create workflow run')\n return run\n}\n\nexport async function getWorkflowRun(id: string, userId?: number | bigint): Promise<WorkflowRunRecord | null> {\n const res = userId == null\n ? await getDb().execute({ sql: 'SELECT * FROM workflow_runs WHERE id = ? LIMIT 1', args: [id] })\n : await getDb().execute({ sql: 'SELECT * FROM workflow_runs WHERE id = ? AND user_id = ? LIMIT 1', args: [id, userId] })\n return res.rows[0] ? rowToWorkflowRun(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function getWorkflowRunByIdempotencyKey(idempotencyKey: string): Promise<WorkflowRunRecord | null> {\n const res = await getDb().execute({ sql: 'SELECT * FROM workflow_runs WHERE idempotency_key = ? LIMIT 1', args: [idempotencyKey] })\n return res.rows[0] ? rowToWorkflowRun(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function listWorkflowRuns(userId: number | bigint, limit = 50): Promise<WorkflowRunRecord[]> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM workflow_runs WHERE user_id = ? ORDER BY queued_at DESC LIMIT ?',\n args: [userId, limit],\n })\n return res.rows.map(r => rowToWorkflowRun(r as unknown as Record<string, unknown>))\n}\n\nexport async function markWorkflowRunRunning(id: string): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = 'running', started_at = COALESCE(started_at, datetime('now')) WHERE id = ?`,\n args: [id],\n })\n}\n\nexport async function completeWorkflowRunRecord(id: string, status: Exclude<WorkflowRunStatus, 'queued' | 'running' | 'cancelled'>, manifest: unknown): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = ?, manifest_json = ?, completed_at = datetime('now') WHERE id = ?`,\n args: [status, JSON.stringify(manifest), id],\n })\n}\n\nexport async function failWorkflowRunRecord(id: string, errorMessage: string, manifest?: unknown): Promise<void> {\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = 'failed', error_message = ?, manifest_json = ?, completed_at = datetime('now') WHERE id = ?`,\n args: [errorMessage, manifest == null ? null : JSON.stringify(manifest), id],\n })\n}\n\nfunction workflowArtifactContentType(kind: string): string {\n if (kind === 'html_report') return 'text/html; charset=utf-8'\n if (kind === 'csv') return 'text/csv; charset=utf-8'\n if (kind === 'json') return 'application/json; charset=utf-8'\n return 'text/plain; charset=utf-8'\n}\n\nexport interface WorkflowArtifactInput {\n kind: string\n label: string\n path: string\n bytes?: number\n rows?: number\n content?: string | null\n}\n\nasync function insertWorkflowArtifact(runId: string, userId: number | bigint, artifact: WorkflowArtifactInput): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO workflow_artifacts (id, run_id, user_id, kind, label, path, content_type, bytes, rows_count, content)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n randomUUID(),\n runId,\n userId,\n artifact.kind,\n artifact.label,\n artifact.path,\n workflowArtifactContentType(artifact.kind),\n artifact.bytes ?? null,\n artifact.rows ?? null,\n artifact.content ?? null,\n ],\n })\n}\n\nexport async function replaceWorkflowArtifacts(runId: string, userId: number | bigint, artifacts: WorkflowArtifactInput[]): Promise<WorkflowArtifactRecord[]> {\n await getDb().execute({ sql: 'DELETE FROM workflow_artifacts WHERE run_id = ? AND user_id = ?', args: [runId, userId] })\n for (const artifact of artifacts) await insertWorkflowArtifact(runId, userId, artifact)\n return listWorkflowArtifacts(runId, userId)\n}\n\nexport async function appendWorkflowArtifacts(runId: string, userId: number | bigint, artifacts: WorkflowArtifactInput[]): Promise<WorkflowArtifactRecord[]> {\n for (const artifact of artifacts) await insertWorkflowArtifact(runId, userId, artifact)\n return listWorkflowArtifacts(runId, userId)\n}\n\nexport async function getWorkflowRunStepState(id: string, userId?: number | bigint): Promise<{ stepIndex: number; totalSteps: number | null; state: unknown; stepLog: WorkflowStepLogEntry[] } | null> {\n const res = userId == null\n ? await getDb().execute({ sql: 'SELECT step_index, total_steps, step_state_json, step_log_json FROM workflow_runs WHERE id = ? LIMIT 1', args: [id] })\n : await getDb().execute({ sql: 'SELECT step_index, total_steps, step_state_json, step_log_json FROM workflow_runs WHERE id = ? AND user_id = ? LIMIT 1', args: [id, userId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n if (!row) return null\n return {\n stepIndex: row.step_index != null ? Number(row.step_index) : 0,\n totalSteps: row.total_steps != null ? Number(row.total_steps) : null,\n state: row.step_state_json != null ? parseNullableJson(row.step_state_json) : null,\n stepLog: parseWorkflowStepLog(row.step_log_json),\n }\n}\n\nexport async function advanceWorkflowRunStep(id: string, input: {\n stepIndex: number\n totalSteps: number\n state: unknown\n status: WorkflowRunStatus\n logEntry: WorkflowStepLogEntry\n appendLog: WorkflowStepLogEntry[]\n}): Promise<void> {\n const stepLog = [...input.appendLog, input.logEntry]\n await getDb().execute({\n sql: `UPDATE workflow_runs SET status = ?, step_index = ?, total_steps = ?, step_state_json = ?, step_log_json = ?, started_at = COALESCE(started_at, datetime('now')) WHERE id = ?`,\n args: [input.status, input.stepIndex, input.totalSteps, JSON.stringify(input.state ?? null), JSON.stringify(stepLog), id],\n })\n}\n\nexport async function listWorkflowArtifacts(runId: string, userId: number | bigint): Promise<WorkflowArtifactRecord[]> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM workflow_artifacts WHERE run_id = ? AND user_id = ? ORDER BY created_at ASC',\n args: [runId, userId],\n })\n return res.rows.map(r => rowToWorkflowArtifact(r as unknown as Record<string, unknown>))\n}\n\nexport async function getWorkflowArtifact(id: string, userId: number | bigint): Promise<WorkflowArtifactRecord | null> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM workflow_artifacts WHERE id = ? AND user_id = ? LIMIT 1',\n args: [id, userId],\n })\n return res.rows[0] ? rowToWorkflowArtifact(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function recordWorkflowWebhookDelivery(input: {\n runId: string\n scheduleId?: string | null\n userId: number | bigint\n webhookUrl: string\n status: 'delivered' | 'failed'\n attempts: number\n lastError?: string | null\n}): Promise<void> {\n await getDb().execute({\n sql: `\n INSERT INTO workflow_webhook_deliveries (\n id, run_id, schedule_id, user_id, webhook_url, status, attempts, last_error, delivered_at\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n args: [\n randomUUID(),\n input.runId,\n input.scheduleId ?? null,\n input.userId,\n input.webhookUrl,\n input.status,\n input.attempts,\n input.lastError ?? null,\n input.status === 'delivered' ? new Date().toISOString() : null,\n ],\n })\n}\n\nexport async function countActiveUsers(): Promise<number> {\n const res = await getDb().execute(`SELECT COUNT(*) as n FROM users WHERE active = 1`)\n return Number(res.rows[0]?.n ?? 0)\n}\n\nexport async function countActiveJobsForUser(userId: number | bigint): Promise<number> {\n const res = await getDb().execute({\n sql: `SELECT COUNT(*) as n FROM jobs WHERE user_id = ? AND (status = 'pending' OR (status = 'running' AND started_at > datetime('now', '-10 minutes')))`,\n args: [userId],\n })\n return Number(res.rows[0]?.n ?? 0)\n}\n\nexport async function claimPendingJob(): Promise<RawJob | undefined> {\n const db = getDb()\n const res = await db.execute(`\n SELECT * FROM jobs\n WHERE status = 'pending'\n AND COALESCE(json_extract(options, '$.executionOwner'), 'cron') != 'inngest'\n ORDER BY created_at\n LIMIT 1\n `)\n const job = res.rows[0] ? rowToRawJob(res.rows[0] as unknown as Record<string, unknown>) : undefined\n if (!job) return undefined\n const upd = await db.execute({\n sql: `UPDATE jobs SET status = 'running', started_at = datetime('now') WHERE id = ? AND status = 'pending'`,\n args: [job.id],\n })\n return upd.rowsAffected === 0 ? undefined : { ...job, status: 'running' }\n}\n\n/** Claim one durable PAA job without allowing a second worker to own it. */\nexport async function claimInngestPaaJob(id: string): Promise<Job | undefined> {\n const db = getDb()\n const current = await getJob(id)\n if (!current || current.options.executionOwner !== 'inngest' || current.options.serpOnly === true) return undefined\n if (current.status === 'running') return undefined\n if (current.status === 'pending') {\n const updated = await db.execute({\n sql: `UPDATE jobs\n SET status = 'running', started_at = COALESCE(started_at, datetime('now')), error = NULL\n WHERE id = ? AND status = 'pending'`,\n args: [id],\n })\n if (updated.rowsAffected === 0) return getJob(id)\n return { ...current, status: 'running', started_at: current.started_at ?? new Date().toISOString() }\n }\n return current\n}\n\n/** Persist recoverable browser progress while keeping the job non-terminal. */\nexport async function checkpointJob(id: string, result: object): Promise<boolean> {\n const updated = await getDb().execute({\n sql: `UPDATE jobs SET result = ? WHERE id = ? AND status = 'running'`,\n args: [JSON.stringify(result), id],\n })\n return updated.rowsAffected === 1\n}\n\nexport async function listPendingInngestPaaJobs(limit = 25): Promise<Job[]> {\n const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)))\n const rows = await getDb().execute({\n sql: `SELECT * FROM jobs\n WHERE status = 'pending'\n AND json_extract(options, '$.executionOwner') = 'inngest'\n AND COALESCE(json_extract(options, '$.serpOnly'), 0) = 0\n ORDER BY created_at\n LIMIT ?`,\n args: [safeLimit],\n })\n return rows.rows.map(row => deserialize(rowToRawJob(row as unknown as Record<string, unknown>)))\n}\n\nexport async function listRecentTerminalInngestPaaJobs(limit = 25): Promise<Job[]> {\n const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)))\n const rows = await getDb().execute({\n sql: `SELECT * FROM jobs\n WHERE status IN ('done', 'failed', 'cancelled')\n AND json_extract(options, '$.executionOwner') = 'inngest'\n AND json_extract(options, '$.billingDebitKey') IS NOT NULL\n AND json_extract(options, '$.billingSettledAt') IS NULL\n ORDER BY completed_at DESC\n LIMIT ?`,\n args: [safeLimit],\n })\n return rows.rows.map(row => deserialize(rowToRawJob(row as unknown as Record<string, unknown>)))\n}\n\nexport async function listStaleRunningInngestPaaJobs(limit = 25): Promise<Job[]> {\n const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)))\n const rows = await getDb().execute({\n sql: `SELECT * FROM jobs\n WHERE status = 'running'\n AND started_at <= datetime('now', '-10 minutes')\n AND json_extract(options, '$.executionOwner') = 'inngest'\n ORDER BY started_at\n LIMIT ?`,\n args: [safeLimit],\n })\n return rows.rows.map(row => deserialize(rowToRawJob(row as unknown as Record<string, unknown>)))\n}\n\nexport async function markJobBillingSettled(id: string, billedMc: number, settledAt = new Date().toISOString()): Promise<boolean> {\n if (!Number.isSafeInteger(billedMc) || billedMc < 0) throw new Error('job billed amount must be a non-negative integer')\n const updated = await getDb().execute({\n sql: `UPDATE jobs\n SET options = json_set(options, '$.billingSettledAt', ?, '$.billedMc', ?)\n WHERE id = ?`,\n args: [settledAt, billedMc, id],\n })\n return updated.rowsAffected === 1\n}\n\nexport async function completeJob(id: string, result: object): Promise<void> {\n await getDb().execute({ sql: `UPDATE jobs SET status = 'done', result = ?, completed_at = datetime('now') WHERE id = ?`, args: [JSON.stringify(result), id] })\n}\n\nexport async function failJob(id: string, error: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE jobs SET status = 'failed', error = ?, completed_at = datetime('now') WHERE id = ?`, args: [error, id] })\n}\n\nexport async function cancelJob(id: string, reason: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE jobs SET status = 'cancelled', error = ?, completed_at = datetime('now') WHERE id = ?`, args: [reason, id] })\n}\n\nexport interface KpoJobRow {\n id: string\n user_id: number\n status: string\n entity_name: string\n entity_type: string\n url: string | null\n request: string\n result: string | null\n error: string | null\n created_at: string\n updated_at: string\n}\n\nexport interface KpoPhaseLogRow {\n id: number\n job_id: string\n phase: string\n output: string\n created_at: string\n}\n\nfunction rowToKpoJob(row: Record<string, unknown>): KpoJobRow {\n return {\n id: String(row.id),\n user_id: Number(row.user_id),\n status: String(row.status),\n entity_name: String(row.entity_name),\n entity_type: String(row.entity_type),\n url: row.url != null ? String(row.url) : null,\n request: String(row.request),\n result: row.result != null ? String(row.result) : null,\n error: row.error != null ? String(row.error) : null,\n created_at: String(row.created_at),\n updated_at: String(row.updated_at),\n }\n}\n\nfunction rowToKpoPhaseLog(row: Record<string, unknown>): KpoPhaseLogRow {\n return {\n id: Number(row.id),\n job_id: String(row.job_id),\n phase: String(row.phase),\n output: String(row.output),\n created_at: String(row.created_at),\n }\n}\n\nexport async function createKpoJob(jobId: string, userId: number, entityName: string, entityType: string, request: object, url?: string): Promise<void> {\n await getDb().execute({\n sql: `INSERT INTO kpo_jobs (id, user_id, status, entity_name, entity_type, url, request) VALUES (?, ?, 'pending', ?, ?, ?, ?)`,\n args: [jobId, userId, entityName, entityType, url ?? null, JSON.stringify(request)],\n })\n}\n\nexport async function claimPendingKpoJob(): Promise<KpoJobRow | null> {\n const db = getDb()\n const upd = await db.execute(\n `UPDATE kpo_jobs SET status='running', updated_at=datetime('now') WHERE id = (SELECT id FROM kpo_jobs WHERE status='pending' ORDER BY created_at ASC LIMIT 1) RETURNING *`\n )\n if (upd.rowsAffected === 0) return null\n return upd.rows[0] ? rowToKpoJob(upd.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function getKpoJob(jobId: string): Promise<KpoJobRow | null> {\n const res = await getDb().execute({ sql: `SELECT * FROM kpo_jobs WHERE id = ?`, args: [jobId] })\n return res.rows[0] ? rowToKpoJob(res.rows[0] as unknown as Record<string, unknown>) : null\n}\n\nexport async function updateKpoJobState(jobId: string, status: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE kpo_jobs SET status = ?, updated_at = datetime('now') WHERE id = ?`, args: [status, jobId] })\n}\n\nexport async function completeKpoJob(jobId: string, result: object): Promise<void> {\n await getDb().execute({ sql: `UPDATE kpo_jobs SET status = 'done', result = ?, updated_at = datetime('now') WHERE id = ?`, args: [JSON.stringify(result), jobId] })\n}\n\nexport async function failKpoJob(jobId: string, error: string): Promise<void> {\n await getDb().execute({ sql: `UPDATE kpo_jobs SET status = 'failed', error = ?, updated_at = datetime('now') WHERE id = ?`, args: [error, jobId] })\n}\n\nexport async function logKpoPhaseComplete(jobId: string, phase: string, output: unknown): Promise<void> {\n await getDb().execute({\n sql: `INSERT INTO kpo_phase_log (job_id, phase, output) VALUES (?, ?, ?)`,\n args: [jobId, phase, JSON.stringify(output)],\n })\n}\n\nexport async function getKpoPhaseLog(jobId: string): Promise<KpoPhaseLogRow[]> {\n const res = await getDb().execute({ sql: `SELECT * FROM kpo_phase_log WHERE job_id = ? ORDER BY created_at ASC`, args: [jobId] })\n return res.rows.map(r => rowToKpoPhaseLog(r as unknown as Record<string, unknown>))\n}\n\nexport async function listKpoJobs(userId?: number): Promise<KpoJobRow[]> {\n const res = userId != null\n ? await getDb().execute({ sql: `SELECT * FROM kpo_jobs WHERE user_id = ? ORDER BY created_at DESC LIMIT 50`, args: [userId] })\n : await getDb().execute(`SELECT * FROM kpo_jobs ORDER BY created_at DESC LIMIT 50`)\n return res.rows.map(r => rowToKpoJob(r as unknown as Record<string, unknown>))\n}\n\nexport async function getUserByStripeCustomerId(customerId: string): Promise<User | undefined> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM users WHERE stripe_customer_id = ? AND active = 1',\n args: [customerId],\n })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport async function setStripeCustomerId(userId: number, customerId: string): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET stripe_customer_id = ? WHERE id = ?',\n args: [customerId, userId],\n })\n}\n\nexport async function setExtraConcurrencySlots(userId: number, slots: number): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET extra_concurrency_slots = ? WHERE id = ?',\n args: [Math.max(0, slots), userId],\n })\n}\n\nexport async function setConcurrencySubId(userId: number, subId: string | null): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET concurrency_stripe_sub_id = ? WHERE id = ?',\n args: [subId, userId],\n })\n}\n\nexport async function setSubscriptionTier(userId: number, tier: string | null, concurrency: number, subId: string | null): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET subscription_tier = ?, subscription_concurrency = ?, subscription_id = ? WHERE id = ?',\n args: [tier, Math.max(0, Math.round(concurrency)), subId, userId],\n })\n}\n\nexport type ConnectedAccountBillingStatus = 'pending' | 'synced' | 'blocked' | 'error'\n\nexport interface ConnectedAccountBillingState {\n user_id: number\n stripe_subscription_id: string | null\n stripe_subscription_item_id: string | null\n price_id: string | null\n quantity: number\n status: ConnectedAccountBillingStatus\n last_error_code: string | null\n checked_at: string\n synced_at: string | null\n updated_at: string\n}\n\nfunction rowToConnectedAccountBillingState(row: Record<string, unknown>): ConnectedAccountBillingState {\n return {\n user_id: Number(row.user_id),\n stripe_subscription_id: row.stripe_subscription_id != null ? String(row.stripe_subscription_id) : null,\n stripe_subscription_item_id: row.stripe_subscription_item_id != null ? String(row.stripe_subscription_item_id) : null,\n price_id: row.price_id != null ? String(row.price_id) : null,\n quantity: Math.max(0, Number(row.quantity ?? 0)),\n status: (['pending', 'synced', 'blocked', 'error'].includes(String(row.status)) ? String(row.status) : 'error') as ConnectedAccountBillingStatus,\n last_error_code: row.last_error_code != null ? String(row.last_error_code) : null,\n checked_at: String(row.checked_at),\n synced_at: row.synced_at != null ? String(row.synced_at) : null,\n updated_at: String(row.updated_at),\n }\n}\n\nexport async function getConnectedAccountBillingState(userId: number | bigint): Promise<ConnectedAccountBillingState | null> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM connected_account_billing WHERE user_id = ? LIMIT 1',\n args: [userId],\n })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return row ? rowToConnectedAccountBillingState(row) : null\n}\n\nexport async function setConnectedAccountBillingState(input: {\n userId: number | bigint\n stripeSubscriptionId: string | null\n stripeSubscriptionItemId: string | null\n priceId: string | null\n quantity: number\n status: ConnectedAccountBillingStatus\n lastErrorCode?: string | null\n synced?: boolean\n}): Promise<ConnectedAccountBillingState> {\n const quantity = Math.max(0, Math.round(input.quantity))\n await getDb().execute({\n sql: `INSERT INTO connected_account_billing\n (user_id, stripe_subscription_id, stripe_subscription_item_id, price_id, quantity, status, last_error_code, checked_at, synced_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), CASE WHEN ? THEN datetime('now') ELSE NULL END, datetime('now'))\n ON CONFLICT(user_id) DO UPDATE SET\n stripe_subscription_id = excluded.stripe_subscription_id,\n stripe_subscription_item_id = excluded.stripe_subscription_item_id,\n price_id = excluded.price_id,\n quantity = excluded.quantity,\n status = excluded.status,\n last_error_code = excluded.last_error_code,\n checked_at = datetime('now'),\n synced_at = CASE WHEN ? THEN datetime('now') ELSE connected_account_billing.synced_at END,\n updated_at = datetime('now')`,\n args: [\n input.userId,\n input.stripeSubscriptionId,\n input.stripeSubscriptionItemId,\n input.priceId,\n quantity,\n input.status,\n input.lastErrorCode ?? null,\n input.synced === true ? 1 : 0,\n input.synced === true ? 1 : 0,\n ],\n })\n const state = await getConnectedAccountBillingState(input.userId)\n if (!state) throw new Error('connected account billing state was not persisted')\n return state\n}\n\nexport async function listUsersDueForConnectedAccountBillingReconciliation(\n staleMinutes = 60,\n limit = 20,\n): Promise<User[]> {\n const modifier = `-${Math.max(1, Math.round(staleMinutes))} minutes`\n const res = await getDb().execute({\n sql: `SELECT u.*\n FROM users u\n LEFT JOIN connected_account_billing b ON b.user_id = u.id\n WHERE u.active = 1\n AND u.subscription_id IS NOT NULL\n AND (b.checked_at IS NULL OR b.checked_at <= datetime('now', ?))\n ORDER BY COALESCE(b.checked_at, u.created_at) ASC\n LIMIT ?`,\n args: [modifier, Math.max(1, Math.min(100, Math.round(limit)))],\n })\n return res.rows.map(row => rowToUser(row as unknown as Record<string, unknown>))\n}\n\nexport async function setMemoryPlan(userId: number, plan: 'free' | 'pro' | 'team', subscriptionId: string | null): Promise<void> {\n await getDb().execute({\n sql: 'UPDATE users SET memory_plan = ?, memory_subscription_id = ? WHERE id = ?',\n args: [plan, subscriptionId, userId],\n })\n}\n\nexport async function getMemoryPlan(userId: number | bigint): Promise<{ plan: string; memory_subscription_id: string | null }> {\n const res = await getDb().execute({ sql: 'SELECT memory_plan, memory_subscription_id FROM users WHERE id = ?', args: [userId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return {\n plan: row?.memory_plan != null ? String(row.memory_plan) : 'free',\n memory_subscription_id: row?.memory_subscription_id != null ? String(row.memory_subscription_id) : null,\n }\n}\n\nexport async function getMemoryProvisioned(userId: number | bigint): Promise<boolean> {\n const res = await getDb().execute({ sql: 'SELECT memory_provisioned FROM users WHERE id = ?', args: [userId] })\n const row = res.rows[0] as unknown as Record<string, unknown> | undefined\n return Number(row?.memory_provisioned ?? 0) === 1\n}\n\nexport async function setMemoryProvisioned(userId: number | bigint, provisioned: boolean): Promise<void> {\n await getDb().execute({ sql: 'UPDATE users SET memory_provisioned = ? WHERE id = ?', args: [provisioned ? 1 : 0, userId] })\n}\n\nexport async function resolveUserByMemorySubscription(subscriptionId: string): Promise<User | undefined> {\n const res = await getDb().execute({ sql: 'SELECT * FROM users WHERE memory_subscription_id = ? AND active = 1', args: [subscriptionId] })\n return res.rows[0] ? rowToUser(res.rows[0] as unknown as Record<string, unknown>) : undefined\n}\n\nexport const CREDIT_LOT_TTL = '+3 months'\n\nasync function ensureMigrated(userId: number): Promise<number> {\n const db = getDb()\n const inserted = await db.execute({\n sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, expires_at)\n SELECT ?, legacy.balance_mc, legacy.balance_mc, 'migration', datetime('now', ?)\n FROM (\n SELECT COALESCE(SUM(amount_mc), 0) AS balance_mc\n FROM ledger\n WHERE user_id = ?\n ) AS legacy\n WHERE legacy.balance_mc > 0\n AND EXISTS (\n SELECT 1 FROM ledger\n WHERE user_id = ? AND created_at < ?\n )\n AND NOT EXISTS (\n SELECT 1 FROM credit_lots WHERE user_id = ?\n )`,\n args: [userId, CREDIT_LOT_TTL, userId, userId, CREDIT_LOTS_EPOCH, userId],\n })\n return inserted.rowsAffected\n}\n\nexport async function expireOldLots(): Promise<{ expired_lots: number; expired_mc: number }> {\n const db = getDb()\n const lots = await db.execute({\n sql: `SELECT id\n FROM credit_lots\n WHERE remaining_mc > 0\n AND never_expires = 0\n AND expires_at <= datetime('now')\n LIMIT 500`,\n })\n if (lots.rows.length === 0) return { expired_lots: 0, expired_mc: 0 }\n const claims: Array<{ token: string; insertResultIndex: number }> = []\n const stmts: Array<{ sql: string; args: Array<string | number> }> = []\n for (const lot of lots.rows) {\n const lotId = Number(lot.id)\n const token = randomUUID()\n claims.push({ token, insertResultIndex: stmts.length })\n stmts.push({\n sql: `INSERT OR IGNORE INTO credit_lot_expirations (lot_id, user_id, amount_mc, claim_token)\n SELECT id, user_id, remaining_mc, ?\n FROM credit_lots\n WHERE id = ?\n AND remaining_mc > 0\n AND never_expires = 0\n AND expires_at <= datetime('now')`,\n args: [token, lotId],\n })\n stmts.push({\n sql: `UPDATE credit_lots\n SET remaining_mc = 0\n WHERE id = ?\n AND EXISTS (\n SELECT 1 FROM credit_lot_expirations\n WHERE lot_id = ? AND claim_token = ?\n )`,\n args: [lotId, lotId, token],\n })\n stmts.push({\n sql: `INSERT INTO ledger (user_id, amount_mc, operation, description)\n SELECT user_id, -amount_mc, 'credit_expired', 'credits expired'\n FROM credit_lot_expirations\n WHERE lot_id = ? AND claim_token = ?`,\n args: [lotId, token],\n })\n }\n const results = await db.batch(stmts, 'write')\n const claimedTokens = claims\n .filter(claim => results[claim.insertResultIndex]?.rowsAffected === 1)\n .map(claim => claim.token)\n if (claimedTokens.length === 0) return { expired_lots: 0, expired_mc: 0 }\n\n const placeholders = claimedTokens.map(() => '?').join(', ')\n const expired = await db.execute({\n sql: `SELECT user_id, amount_mc\n FROM credit_lot_expirations\n WHERE claim_token IN (${placeholders})`,\n args: claimedTokens,\n })\n const users = new Set(expired.rows.map(row => Number(row.user_id)))\n for (const uid of users) await reconcileBalanceMc(uid)\n return {\n expired_lots: expired.rows.length,\n expired_mc: expired.rows.reduce((sum, row) => sum + Number(row.amount_mc), 0),\n }\n}\n\nexport async function migrateExistingBalancesToLots(): Promise<{ migrated: number; total_mc: number }> {\n const db = getDb()\n const res = await db.execute({\n sql: `SELECT DISTINCT u.id AS user_id\n FROM users u\n JOIN ledger l ON l.user_id = u.id\n WHERE l.created_at < ?\n AND NOT EXISTS (SELECT 1 FROM credit_lots WHERE user_id = u.id)`,\n args: [CREDIT_LOTS_EPOCH],\n })\n let migrated = 0\n let totalMc = 0\n for (const row of res.rows) {\n const uid = Number(row.user_id)\n const inserted = await ensureMigrated(uid)\n if (inserted === 0) continue\n const lot = await db.execute({\n sql: `SELECT amount_mc FROM credit_lots\n WHERE user_id = ? AND source = 'migration'\n ORDER BY id DESC LIMIT 1`,\n args: [uid],\n })\n const amountMc = Number(lot.rows[0]?.amount_mc ?? 0)\n await reconcileBalanceMc(uid)\n migrated += 1\n totalMc += amountMc\n }\n return { migrated, total_mc: totalMc }\n}\n\nexport async function creditMc(\n userId: number,\n mc: number,\n operation: string,\n description?: string,\n stripePaymentIntent?: string,\n options?: { neverExpires?: boolean },\n): Promise<number> {\n const db = getDb()\n await ensureMigrated(userId)\n let neverExpires = options?.neverExpires\n if (neverExpires === undefined && mc > 0) {\n const tier = await db.execute({ sql: 'SELECT subscription_tier FROM users WHERE id = ? LIMIT 1', args: [userId] })\n neverExpires = String(tier.rows[0]?.subscription_tier ?? '').startsWith('sonic')\n }\n const expiry = neverExpires ? '9999-12-31 23:59:59' : null\n\n if (stripePaymentIntent) {\n const stmts: Array<{ sql: string; args: Array<string | number | null> }> = [{\n sql: 'INSERT OR IGNORE INTO ledger (user_id, amount_mc, operation, description, stripe_pi) VALUES (?, ?, ?, ?, ?)',\n args: [userId, mc, operation, description ?? null, stripePaymentIntent],\n }]\n if (mc > 0) {\n stmts.push({\n sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires)\n SELECT ?, ?, ?, ?, ?, COALESCE(?, datetime('now', ?)), ?\n WHERE changes() = 1`,\n args: [userId, mc, mc, operation, stripePaymentIntent, expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0],\n })\n }\n stmts.push({\n sql: 'UPDATE users SET balance_mc = balance_mc + ? WHERE id = ? AND changes() = 1',\n args: [mc, userId],\n })\n await db.batch(stmts, 'write')\n const res = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return Number(res.rows[0]?.balance_mc ?? 0)\n }\n\n const stmts: Array<{ sql: string; args: Array<string | number | null> }> = []\n if (mc > 0) {\n stmts.push({ sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires) VALUES (?, ?, ?, ?, ?, COALESCE(?, datetime('now', ?)), ?)`, args: [userId, mc, mc, operation, null, expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0] })\n }\n stmts.push({ sql: 'UPDATE users SET balance_mc = balance_mc + ? WHERE id = ?', args: [mc, userId] })\n stmts.push({ sql: 'INSERT INTO ledger (user_id, amount_mc, operation, description, stripe_pi) VALUES (?, ?, ?, ?, ?)', args: [userId, mc, operation, description ?? null, null] })\n await db.batch(stmts, 'write')\n const res = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return Number(res.rows[0]?.balance_mc ?? 0)\n}\n\nexport async function creditMcIdempotent(\n userId: number,\n mc: number,\n operation: string,\n description: string,\n idempotencyKey: string,\n options?: { neverExpires?: boolean },\n): Promise<{ ok: boolean; balance_mc: number; duplicate: boolean }> {\n if (!Number.isSafeInteger(mc) || mc <= 0) throw new Error('idempotent credit amount must be a positive integer')\n if (!idempotencyKey || idempotencyKey.length > 500) throw new Error('idempotent credit key is required and must be bounded')\n\n const db = getDb()\n await ensureMigrated(userId)\n\n let neverExpires = options?.neverExpires\n if (neverExpires === undefined) {\n const tier = await db.execute({ sql: 'SELECT subscription_tier FROM users WHERE id = ? LIMIT 1', args: [userId] })\n neverExpires = String(tier.rows[0]?.subscription_tier ?? '').startsWith('sonic')\n }\n const expiry = neverExpires ? '9999-12-31 23:59:59' : null\n\n const readBalance = async (): Promise<number> => {\n const res = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return Number(res.rows[0]?.balance_mc ?? 0)\n }\n\n let results\n try {\n results = await db.batch([\n {\n sql: \"INSERT OR IGNORE INTO billing_credits (idempotency_key, user_id, amount_mc, operation, description, status) VALUES (?, ?, ?, ?, ?, 'claimed')\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: \"UPDATE billing_credits SET status = 'applying' WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed'\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: `INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, stripe_pi, expires_at, never_expires)\n SELECT user_id, amount_mc, amount_mc, operation, NULL, COALESCE(?, datetime('now', ?)), ?\n FROM billing_credits\n WHERE idempotency_key = ? AND status = 'applying'`,\n args: [expiry, CREDIT_LOT_TTL, neverExpires ? 1 : 0, idempotencyKey],\n },\n {\n sql: `UPDATE users\n SET balance_mc = balance_mc + COALESCE((\n SELECT amount_mc FROM billing_credits WHERE idempotency_key = ? AND status = 'applying'\n ), 0)\n WHERE id = ?`,\n args: [idempotencyKey, userId],\n },\n {\n sql: \"INSERT INTO ledger (user_id, amount_mc, operation, description) SELECT user_id, amount_mc, operation, description FROM billing_credits WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n {\n sql: \"UPDATE billing_credits SET status = 'applied', applied_at = datetime('now') WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n ], 'write')\n } catch (error) {\n try {\n const recovered = await db.execute({\n sql: \"SELECT user_id, amount_mc, operation, description FROM billing_credits WHERE idempotency_key = ? AND status = 'applied' LIMIT 1\",\n args: [idempotencyKey],\n })\n const row = recovered.rows[0]\n if (\n row\n && Number(row.user_id) === userId\n && Number(row.amount_mc) === mc\n && String(row.operation) === operation\n && String(row.description) === description\n ) {\n return { ok: true, balance_mc: await readBalance(), duplicate: true }\n }\n } catch {\n void 0\n }\n throw error\n }\n\n const claim = await db.execute({\n sql: 'SELECT user_id, amount_mc, operation, description, status FROM billing_credits WHERE idempotency_key = ? LIMIT 1',\n args: [idempotencyKey],\n })\n const row = claim.rows[0]\n if (\n !row\n || Number(row.user_id) !== userId\n || Number(row.amount_mc) !== mc\n || String(row.operation) !== operation\n || String(row.description) !== description\n ) {\n throw new Error('idempotent credit key conflicts with another credit')\n }\n\n return {\n ok: String(row.status) === 'applied',\n balance_mc: await readBalance(),\n duplicate: results[1]?.rowsAffected === 0 && String(row.status) === 'applied',\n }\n}\n\nexport async function ledgerExistsForOperation(userId: number, operation: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: 'SELECT 1 FROM ledger WHERE user_id = ? AND operation = ? LIMIT 1',\n args: [userId, operation],\n })\n return res.rows.length > 0\n}\n\nexport async function claimMonthlyFreeRefresh(userId: number, month: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: 'INSERT OR IGNORE INTO free_credit_refreshes (user_id, month) VALUES (?, ?)',\n args: [userId, month],\n })\n return res.rowsAffected > 0\n}\n\nexport async function debitMc(\n userId: number,\n mc: number,\n operation: string,\n description?: string,\n): Promise<{ ok: boolean; balance_mc: number }> {\n const debit = await debitMcIdempotent(\n userId,\n mc,\n operation,\n description ?? '',\n `debit:${randomUUID()}`,\n )\n return { ok: debit.ok, balance_mc: debit.balance_mc }\n}\n\n/**\n * Atomically applies a wallet debit once for a stable billing key.\n *\n * The debit claim, wallet update, credit-lot drain, and ledger row are committed\n * in one libSQL batch. A claim that could not be funded remains retryable, while\n * a completed claim makes retries a read-only success. This is intended for\n * billing lifecycles that must be recoverable after a caller or process crash.\n */\nexport async function debitMcIdempotent(\n userId: number,\n mc: number,\n operation: string,\n description: string,\n idempotencyKey: string,\n): Promise<{ ok: boolean; balance_mc: number; duplicate: boolean }> {\n if (!Number.isSafeInteger(mc) || mc <= 0) throw new Error('idempotent debit amount must be a positive integer')\n if (!idempotencyKey || idempotencyKey.length > 500) throw new Error('idempotent debit key is required and must be bounded')\n\n const db = getDb()\n await ensureMigrated(userId)\n let results\n try {\n results = await db.batch([\n {\n // An unfunded claim may be retried with a corrected estimate under the\n // same logical operation fingerprint. Applied claims remain immutable.\n sql: \"UPDATE billing_debits SET amount_mc = ? WHERE idempotency_key = ? AND user_id = ? AND operation = ? AND description = ? AND status = 'claimed'\",\n args: [mc, idempotencyKey, userId, operation, description],\n },\n {\n sql: \"INSERT OR IGNORE INTO billing_debits (idempotency_key, user_id, amount_mc, operation, description, status) VALUES (?, ?, ?, ?, ?, 'claimed')\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: `UPDATE users\n SET balance_mc = balance_mc - ?\n WHERE id = ?\n AND balance_mc >= ?\n AND (SELECT COALESCE(SUM(remaining_mc), 0)\n FROM credit_lots\n WHERE user_id = ?\n AND remaining_mc > 0\n AND (never_expires = 1 OR expires_at > datetime('now'))) >= ?\n AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed')`,\n args: [mc, userId, mc, userId, mc, idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: \"UPDATE billing_debits SET status = 'applying' WHERE idempotency_key = ? AND user_id = ? AND amount_mc = ? AND operation = ? AND description = ? AND status = 'claimed' AND changes() = 1\",\n args: [idempotencyKey, userId, mc, operation, description],\n },\n {\n sql: `INSERT OR IGNORE INTO billing_debit_allocations\n (debit_idempotency_key, lot_id, user_id, amount_mc, expires_at)\n WITH ordered AS MATERIALIZED (\n SELECT id,\n remaining_mc,\n expires_at,\n COALESCE(SUM(remaining_mc) OVER (\n ORDER BY never_expires ASC, expires_at ASC, granted_at ASC, id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM credit_lots\n WHERE user_id = ? AND remaining_mc > 0 AND (never_expires = 1 OR expires_at > datetime('now'))\n ), allocation AS (\n SELECT id, expires_at, MIN(remaining_mc, MAX(0, ? - prior_mc)) AS take_mc\n FROM ordered\n )\n SELECT ?, id, ?, take_mc, expires_at\n FROM allocation\n WHERE take_mc > 0\n AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,\n args: [userId, mc, idempotencyKey, userId, idempotencyKey],\n },\n {\n sql: `UPDATE credit_lots\n SET remaining_mc = remaining_mc - COALESCE((\n SELECT amount_mc FROM billing_debit_allocations\n WHERE debit_idempotency_key = ? AND lot_id = credit_lots.id\n ), 0)\n WHERE id IN (\n SELECT lot_id FROM billing_debit_allocations WHERE debit_idempotency_key = ?\n )\n AND EXISTS (SELECT 1 FROM billing_debits WHERE idempotency_key = ? AND status = 'applying')`,\n args: [idempotencyKey, idempotencyKey, idempotencyKey],\n },\n {\n sql: \"INSERT INTO ledger (user_id, amount_mc, operation, description) SELECT user_id, -amount_mc, operation, description FROM billing_debits WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n {\n sql: \"UPDATE billing_debits SET status = 'applied', applied_at = datetime('now') WHERE idempotency_key = ? AND status = 'applying'\",\n args: [idempotencyKey],\n },\n ], 'write')\n } catch (error) {\n // libSQL can report a transport failure after the transaction committed.\n // Read the durable receipt before surfacing the error so callers do not\n // retry a completed debit under a different key.\n try {\n const recovered = await db.execute({\n sql: \"SELECT user_id, amount_mc, operation, description, status FROM billing_debits WHERE idempotency_key = ? AND status = 'applied' LIMIT 1\",\n args: [idempotencyKey],\n })\n const row = recovered.rows[0]\n if (\n row\n && Number(row.user_id) === userId\n && Number(row.amount_mc) === mc\n && String(row.operation) === operation\n && String(row.description) === description\n ) {\n const balance = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return { ok: true, balance_mc: Number(balance.rows[0]?.balance_mc ?? 0), duplicate: true }\n }\n } catch {\n // Preserve the original batch error when the recovery read also fails.\n }\n throw error\n }\n\n const claim = await db.execute({\n sql: 'SELECT user_id, amount_mc, operation, description, status FROM billing_debits WHERE idempotency_key = ? LIMIT 1',\n args: [idempotencyKey],\n })\n const row = claim.rows[0]\n if (\n !row\n || Number(row.user_id) !== userId\n || Number(row.amount_mc) !== mc\n || String(row.operation) !== operation\n || String(row.description) !== description\n ) {\n throw new Error('idempotent debit key conflicts with another debit')\n }\n const balance = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return {\n ok: String(row.status) === 'applied',\n balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),\n duplicate: results[2]?.rowsAffected === 0 && String(row.status) === 'applied',\n }\n}\n\nexport interface IdempotentDebitSettlement {\n balance_mc: number\n final_amount_mc: number\n /** Credits actually restored to the spendable wallet. */\n refund_mc: number\n /** Unused hold that had already expired and therefore was not restored. */\n expired_refund_mc?: number\n duplicate: boolean\n}\n\n/**\n * Atomically settles an idempotent debit hold to its final charge.\n *\n * The first settlement for a hold wins and is persisted as a billing-event\n * receipt. Retrying the same settlement, including after an ambiguous\n * post-commit transport error, reads that receipt and cannot apply a second\n * refund. A later retry that observes a different runtime cost receives the\n * first durable final amount so its public billing result remains consistent\n * with the wallet.\n */\nexport async function settleDebitMcIdempotent(\n userId: number,\n debitIdempotencyKey: string,\n finalAmountMc: number,\n refundOperation: string,\n description: string,\n sourceSurface: string,\n): Promise<IdempotentDebitSettlement> {\n if (!Number.isSafeInteger(finalAmountMc) || finalAmountMc < 0) {\n throw new Error('idempotent debit settlement amount must be a non-negative integer')\n }\n if (!debitIdempotencyKey || debitIdempotencyKey.length > 500) {\n throw new Error('idempotent debit settlement key is required and must be bounded')\n }\n if (!refundOperation || !sourceSurface) {\n throw new Error('idempotent debit settlement operation and source are required')\n }\n\n const db = getDb()\n await ensureMigrated(userId)\n const settlementKey = `debit-settlement:${createHash('sha256').update(debitIdempotencyKey).digest('hex')}`\n const billingClass = 'wallet_hold_settlement'\n const eventId = randomUUID()\n const metadata = JSON.stringify({ debitIdempotencyKey, refundOperation, description })\n\n const readReceipt = async (): Promise<IdempotentDebitSettlement | null> => {\n const receipt = await db.execute({\n sql: `SELECT e.user_id,\n e.status,\n e.amount_mc AS final_amount_mc,\n e.billing_class,\n e.source_surface,\n e.settled_at,\n d.amount_mc AS held_amount_mc\n FROM billing_events e\n JOIN billing_debits d ON d.idempotency_key = ?\n WHERE e.idempotency_key = ?\n AND d.user_id = e.user_id\n AND d.status = 'applied'\n LIMIT 1`,\n args: [debitIdempotencyKey, settlementKey],\n })\n const row = receipt.rows[0]\n if (!row) return null\n if (\n Number(row.user_id) !== userId\n || String(row.billing_class) !== billingClass\n || String(row.source_surface) !== sourceSurface\n ) {\n throw new Error('idempotent debit settlement key conflicts with another settlement')\n }\n if (String(row.status) !== 'settled') return null\n const heldAmountMc = Number(row.held_amount_mc)\n const durableFinalAmountMc = Number(row.final_amount_mc)\n if (\n !Number.isSafeInteger(heldAmountMc)\n || !Number.isSafeInteger(durableFinalAmountMc)\n || durableFinalAmountMc < 0\n || durableFinalAmountMc > heldAmountMc\n ) {\n throw new Error('idempotent debit settlement receipt is invalid')\n }\n const nominalRefundMc = heldAmountMc - durableFinalAmountMc\n const allocationRefund = await db.execute({\n sql: `WITH ordered AS (\n SELECT a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n )\n SELECT (SELECT COUNT(*) FROM ordered) AS allocation_count,\n COALESCE(SUM(CASE WHEN expires_at > ? THEN refund_mc ELSE 0 END), 0) AS credited_refund_mc\n FROM refunds`,\n args: [debitIdempotencyKey, durableFinalAmountMc, String(row.settled_at)],\n })\n const allocationCount = Number(allocationRefund.rows[0]?.allocation_count ?? 0)\n const creditedRefundMc = allocationCount > 0\n ? Number(allocationRefund.rows[0]?.credited_refund_mc ?? 0)\n : 0\n const balance = await db.execute({ sql: 'SELECT balance_mc FROM users WHERE id = ?', args: [userId] })\n return {\n balance_mc: Number(balance.rows[0]?.balance_mc ?? 0),\n final_amount_mc: durableFinalAmountMc,\n refund_mc: creditedRefundMc,\n expired_refund_mc: nominalRefundMc - creditedRefundMc,\n duplicate: true,\n }\n }\n\n let results\n try {\n results = await db.batch([\n {\n sql: `INSERT OR IGNORE INTO billing_events\n (id, user_id, idempotency_key, billing_class, source_surface, status, amount_mc, metadata)\n VALUES (?, ?, ?, ?, ?, 'settling', ?, ?)`,\n args: [eventId, userId, settlementKey, billingClass, sourceSurface, finalAmountMc, metadata],\n },\n {\n sql: `WITH ordered AS (\n SELECT a.lot_id,\n a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT lot_id,\n expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n )\n UPDATE users\n SET balance_mc = balance_mc + CASE\n WHEN EXISTS (SELECT 1 FROM ordered) THEN COALESCE(\n (SELECT SUM(refund_mc) FROM refunds WHERE expires_at > datetime('now')),\n 0\n )\n -- Holds created before allocation provenance cannot be safely\n -- tied back to a source lot. Treat their unused portion as\n -- expired instead of minting spendable credits with a guessed\n -- expiry.\n ELSE 0\n END\n WHERE id = ?\n AND EXISTS (\n SELECT 1\n FROM billing_events e\n JOIN billing_debits d ON d.idempotency_key = ?\n WHERE e.idempotency_key = ?\n AND e.user_id = ?\n AND e.billing_class = ?\n AND e.source_surface = ?\n AND e.status = 'settling'\n AND d.user_id = e.user_id\n AND d.status = 'applied'\n AND e.amount_mc BETWEEN 0 AND d.amount_mc\n )`,\n args: [\n debitIdempotencyKey,\n finalAmountMc,\n userId,\n debitIdempotencyKey,\n settlementKey,\n userId,\n billingClass,\n sourceSurface,\n ],\n },\n {\n sql: `UPDATE billing_events\n SET status = 'applying'\n WHERE idempotency_key = ?\n AND user_id = ?\n AND billing_class = ?\n AND source_surface = ?\n AND status = 'settling'\n AND changes() = 1`,\n args: [settlementKey, userId, billingClass, sourceSurface],\n },\n {\n sql: `WITH ordered AS (\n SELECT a.lot_id,\n a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT lot_id,\n expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n )\n UPDATE credit_lots\n SET remaining_mc = remaining_mc + COALESCE((SELECT refund_mc FROM refunds WHERE refunds.lot_id = credit_lots.id), 0)\n WHERE id IN (\n SELECT lot_id FROM refunds\n WHERE refund_mc > 0 AND expires_at > datetime('now')\n )\n AND EXISTS (SELECT 1 FROM billing_events WHERE idempotency_key = ? AND status = 'applying')`,\n args: [debitIdempotencyKey, finalAmountMc, settlementKey],\n },\n {\n sql: `WITH ordered AS (\n SELECT a.amount_mc,\n a.expires_at,\n COALESCE(SUM(a.amount_mc) OVER (\n ORDER BY a.expires_at ASC, a.lot_id ASC\n ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING\n ), 0) AS prior_mc\n FROM billing_debit_allocations a\n WHERE a.debit_idempotency_key = ?\n ), refunds AS (\n SELECT expires_at,\n amount_mc - MIN(amount_mc, MAX(0, ? - prior_mc)) AS refund_mc\n FROM ordered\n ), credited AS (\n SELECT e.user_id,\n CASE\n WHEN EXISTS (SELECT 1 FROM ordered) THEN COALESCE(\n (SELECT SUM(refund_mc) FROM refunds WHERE expires_at > datetime('now')),\n 0\n )\n ELSE 0\n END AS refund_mc\n FROM billing_events e\n JOIN billing_debits d ON d.idempotency_key = ?\n WHERE e.idempotency_key = ?\n AND e.status = 'applying'\n )\n INSERT INTO ledger (user_id, amount_mc, operation, description)\n SELECT user_id,\n refund_mc,\n ?,\n ?\n FROM credited\n WHERE refund_mc > 0`,\n args: [\n debitIdempotencyKey,\n finalAmountMc,\n debitIdempotencyKey,\n settlementKey,\n refundOperation,\n description,\n ],\n },\n {\n sql: `UPDATE billing_events\n SET status = 'settled', settled_at = datetime('now')\n WHERE idempotency_key = ?\n AND status = 'applying'`,\n args: [settlementKey],\n },\n ], 'write')\n } catch (error) {\n try {\n const recovered = await readReceipt()\n if (recovered) return recovered\n } catch {\n // Preserve the original batch error when the recovery read also fails.\n }\n throw error\n }\n\n const receipt = await readReceipt()\n if (!receipt) throw new Error('idempotent debit settlement did not reach a durable settled state')\n return { ...receipt, duplicate: results[0]?.rowsAffected === 0 }\n}\n\nexport async function getLedger(userId: number, limit = 50): Promise<LedgerRow[]> {\n const res = await getDb().execute({\n sql: 'SELECT * FROM ledger WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',\n args: [userId, limit],\n })\n return res.rows.map(r => ({\n id: Number(r.id),\n user_id: Number(r.user_id),\n amount_mc: Number(r.amount_mc),\n operation: String(r.operation),\n description: r.description != null ? String(r.description) : null,\n stripe_pi: r.stripe_pi != null ? String(r.stripe_pi) : null,\n created_at: String(r.created_at),\n }))\n}\n\nexport interface LedgerOperationClassification {\n refund: string[]\n grant: string[]\n purchase: string[]\n}\n\nexport interface BillingLedgerSummary {\n spent_mc: number\n refunded_mc: number\n granted_mc: number\n purchased_mc: number\n net_mc: number\n by_operation: Array<{ operation: string; mc: number; count: number }>\n}\n\nexport async function getBillingLedgerSummary(\n userId: number,\n since: string,\n classification: LedgerOperationClassification,\n): Promise<BillingLedgerSummary> {\n const db = getDb()\n const refundPlaceholders = classification.refund.map(() => '?').join(', ')\n const grantPlaceholders = classification.grant.map(() => '?').join(', ')\n const purchasePlaceholders = classification.purchase.map(() => '?').join(', ')\n const totalsRes = await db.execute({\n sql: `SELECT\n COALESCE(SUM(CASE WHEN amount_mc < 0 AND operation NOT IN (${refundPlaceholders}) THEN -amount_mc ELSE 0 END), 0) AS spent_mc,\n COALESCE(SUM(CASE WHEN amount_mc > 0 AND operation IN (${refundPlaceholders}) THEN amount_mc ELSE 0 END), 0) AS refunded_mc,\n COALESCE(SUM(CASE WHEN amount_mc > 0 AND operation IN (${grantPlaceholders}) THEN amount_mc ELSE 0 END), 0) AS granted_mc,\n COALESCE(SUM(CASE WHEN amount_mc > 0 AND operation IN (${purchasePlaceholders}) THEN amount_mc ELSE 0 END), 0) AS purchased_mc,\n COALESCE(SUM(amount_mc), 0) AS net_mc\n FROM ledger WHERE user_id = ? AND created_at >= ?`,\n args: [\n ...classification.refund, ...classification.refund,\n ...classification.grant, ...classification.purchase,\n userId, since,\n ],\n })\n const byOpRes = await db.execute({\n sql: `SELECT operation, -SUM(amount_mc) AS mc, COUNT(*) AS count\n FROM ledger\n WHERE user_id = ? AND created_at >= ? AND amount_mc < 0 AND operation NOT IN (${refundPlaceholders})\n GROUP BY operation\n ORDER BY mc DESC, operation ASC`,\n args: [userId, since, ...classification.refund],\n })\n const totals = totalsRes.rows[0]\n return {\n spent_mc: Number(totals?.spent_mc ?? 0),\n refunded_mc: Number(totals?.refunded_mc ?? 0),\n granted_mc: Number(totals?.granted_mc ?? 0),\n purchased_mc: Number(totals?.purchased_mc ?? 0),\n net_mc: Number(totals?.net_mc ?? 0),\n by_operation: byOpRes.rows.map(r => ({\n operation: String(r.operation),\n mc: Number(r.mc),\n count: Number(r.count),\n })),\n }\n}\n\nexport async function ledgerExistsForStripePI(stripePI: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: 'SELECT 1 FROM ledger WHERE stripe_pi = ? LIMIT 1',\n args: [stripePI],\n })\n return res.rows.length > 0\n}\n\nexport async function reconcileBalanceMc(userId: number | bigint, knownBalanceMc?: number): Promise<number> {\n const db = getDb()\n // The caller's value is only a fast-path hint. Every write compares against\n // the cached balance observed in the same snapshot as the lot projection.\n void knownBalanceMc\n for (let attempt = 0; attempt < 8; attempt += 1) {\n const snapshot = await db.execute({\n sql: `SELECT\n u.balance_mc AS cached_balance_mc,\n COUNT(l.id) AS lot_count,\n COALESCE(SUM(CASE\n WHEN l.remaining_mc > 0\n AND (l.never_expires = 1 OR l.expires_at > datetime('now'))\n THEN l.remaining_mc ELSE 0 END), 0) AS projected_balance_mc\n FROM users u\n LEFT JOIN credit_lots l ON l.user_id = u.id\n WHERE u.id = ?\n GROUP BY u.id, u.balance_mc`,\n args: [userId],\n })\n const row = snapshot.rows[0]\n if (!row) return 0\n\n if (Number(row.lot_count ?? 0) === 0) {\n await ensureMigrated(Number(userId))\n const nowHasLots = await db.execute({\n sql: 'SELECT 1 FROM credit_lots WHERE user_id = ? LIMIT 1',\n args: [userId],\n })\n if (nowHasLots.rows.length > 0) continue\n }\n\n const cachedBalanceMc = Number(row.cached_balance_mc ?? 0)\n const projectedBalanceMc = Number(row.projected_balance_mc ?? 0)\n if (cachedBalanceMc === projectedBalanceMc) return projectedBalanceMc\n\n const updated = await db.execute({\n sql: 'UPDATE users SET balance_mc = ? WHERE id = ? AND balance_mc = ?',\n args: [projectedBalanceMc, userId, cachedBalanceMc],\n })\n if (updated.rowsAffected === 1) return projectedBalanceMc\n }\n\n // Under sustained contention, return the last committed wallet balance\n // instead of risking a stale projection write.\n const current = await db.execute({\n sql: 'SELECT balance_mc FROM users WHERE id = ? LIMIT 1',\n args: [userId],\n })\n return Number(current.rows[0]?.balance_mc ?? 0)\n}\n\nexport interface LedgerRow {\n id: number\n user_id: number\n amount_mc: number\n operation: string\n description: string | null\n stripe_pi: string | null\n created_at: string\n}\n\nexport const SiteAuditJobRowSchema = z.object({\n id: z.string(),\n user_id: z.number(),\n status: z.string().default('pending'),\n client_domain: z.string(),\n session_path: z.string(),\n request: z.string(),\n result: z.string().nullable(),\n error: z.string().nullable(),\n created_at: z.string(),\n updated_at: z.string(),\n});\nexport type SiteAuditJobRow = z.infer<typeof SiteAuditJobRowSchema>;\n\nexport const SiteAuditPhaseLogRowSchema = z.object({\n id: z.number(),\n job_id: z.string(),\n phase: z.string(),\n completed_at: z.string(),\n output_summary: z.string(),\n});\nexport type SiteAuditPhaseLogRow = z.infer<typeof SiteAuditPhaseLogRowSchema>;\n\nexport interface InboxMessageSummary {\n id: string\n subject: string\n sent_at: string\n opened_at: string | null\n}\n\nexport interface InboxMessage extends InboxMessageSummary {\n body_md: string\n source: string\n}\n\nexport async function listInboxMessages(userId: number | bigint): Promise<InboxMessageSummary[]> {\n const res = await getDb().execute({\n sql: 'SELECT id, subject, sent_at, opened_at FROM inbox_messages WHERE user_id = ? AND deleted_at IS NULL ORDER BY sent_at DESC, id DESC',\n args: [userId],\n })\n return res.rows.map(r => ({\n id: String(r.id),\n subject: String(r.subject),\n sent_at: String(r.sent_at),\n opened_at: r.opened_at != null ? String(r.opened_at) : null,\n }))\n}\n\nexport async function getInboxMessage(userId: number | bigint, id: string): Promise<InboxMessage | undefined> {\n const res = await getDb().execute({\n sql: 'SELECT id, subject, body_md, source, sent_at, opened_at FROM inbox_messages WHERE id = ? AND user_id = ? AND deleted_at IS NULL',\n args: [id, userId],\n })\n const row = res.rows[0]\n if (!row) return undefined\n return {\n id: String(row.id),\n subject: String(row.subject),\n body_md: String(row.body_md),\n source: String(row.source),\n sent_at: String(row.sent_at),\n opened_at: row.opened_at != null ? String(row.opened_at) : null,\n }\n}\n\nexport async function markInboxMessageOpened(userId: number | bigint, id: string): Promise<string | null> {\n const db = getDb()\n await db.execute({\n sql: \"UPDATE inbox_messages SET opened_at = datetime('now') WHERE id = ? AND user_id = ? AND deleted_at IS NULL AND opened_at IS NULL\",\n args: [id, userId],\n })\n const res = await db.execute({\n sql: 'SELECT opened_at FROM inbox_messages WHERE id = ? AND user_id = ? AND deleted_at IS NULL',\n args: [id, userId],\n })\n const row = res.rows[0]\n if (!row) return null\n return row.opened_at != null ? String(row.opened_at) : null\n}\n\nexport async function softDeleteInboxMessage(userId: number | bigint, id: string): Promise<boolean> {\n const res = await getDb().execute({\n sql: \"UPDATE inbox_messages SET deleted_at = datetime('now') WHERE id = ? AND user_id = ? AND deleted_at IS NULL\",\n args: [id, userId],\n })\n return res.rowsAffected > 0\n}\n\nexport async function insertInboxMessage(userId: number | bigint, subject: string, bodyMd: string, source = 'operator'): Promise<string> {\n const id = randomUUID()\n await getDb().execute({\n sql: 'INSERT INTO inbox_messages (id, user_id, subject, body_md, source) VALUES (?, ?, ?, ?, ?)',\n args: [id, userId, subject, bodyMd, source],\n })\n return id\n}\n\nexport async function listActiveUserIds(): Promise<number[]> {\n const res = await getDb().execute('SELECT id FROM users WHERE active = 1')\n return res.rows.map(r => Number(r.id))\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\n\nexport interface CostContext {\n op: string\n probeRunId?: string | null\n userId?: number | null\n forceHeadless?: boolean\n forceHeadful?: boolean\n subOp?: string | null\n headlessSentOut?: { value: boolean | null }\n}\n\nconst storage = new AsyncLocalStorage<CostContext>()\n\nexport function runWithCostContext<T>(ctx: CostContext, fn: () => T): T {\n return storage.run(ctx, fn)\n}\n\nexport function currentCostContext(): CostContext | undefined {\n return storage.getStore()\n}\n","export const KERNEL_HEADLESS_USD_PER_SEC = 0.0000166667\nexport const KERNEL_HEADFUL_USD_PER_SEC = 0.0001333336\n\nexport const HEADLESS_OPS = new Set<string>(['serp', 'fb_search', 'fb_ad', 'instagram'])\n\nexport const FAL_WIZPER_USD_PER_COMPUTE_SEC = 0.00111\nexport const FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR = 4 // calibrated from 2 real Fal invoices (4.36x, 6.18x wall-clock vs actual)\nexport const DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS = 0.0004\n\nfunction envRate(name: string, fallback: number): number {\n const raw = process.env[name]?.trim()\n if (!raw) return fallback\n const parsed = Number(raw)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback\n}\n\nexport const NANGO_USD_PER_CONNECTION_MONTH = envRate('NANGO_USD_PER_CONNECTION_MONTH', 1)\nexport const NANGO_USD_PER_FUNCTION_RUN = envRate('NANGO_USD_PER_FUNCTION_RUN', 0.0001)\nexport const NANGO_USD_PER_PROXY_REQUEST = envRate('NANGO_USD_PER_PROXY_REQUEST', 0.0001)\nexport const NANGO_USD_PER_COMPUTE_SEC = envRate('NANGO_USD_PER_COMPUTE_SEC', 0.0002)\n\nexport const CONNECTED_TARGET_MARGIN_MULTIPLE = 2.25\n\nexport function kernelCostUsd(ms: number, headful: boolean): number {\n const sec = Math.max(0, ms) / 1000\n return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC)\n}\n\nexport function vendorCostUsd(vendor: string, units: number): number {\n if (vendor === 'fal_wizper') {\n const estimatedComputeSec = Math.max(0, units) / FAL_WIZPER_WALLCLOCK_TO_COMPUTE_SEC_FACTOR\n return estimatedComputeSec * FAL_WIZPER_USD_PER_COMPUTE_SEC\n }\n if (vendor === 'deepinfra_qwen') return (Math.max(0, units) / 1000) * DEEPINFRA_QWEN_USD_PER_1K_OUT_TOKENS\n if (vendor === 'openrouter') return Math.max(0, units) // units IS the real USD cost OpenRouter reported, not a rate-multiplied estimate\n if (vendor === 'mcp_memory_video') return Math.max(0, units) // units IS the real costUsd mcp-memory's video pipeline reported (OpenRouter + transcription)\n if (vendor === 'mcp_memory_ai') return Math.max(0, units) // units IS the real costUsd mcp-memory reported via /ai-debit\n if (vendor === 'nango_connection') return Math.max(0, units) * NANGO_USD_PER_CONNECTION_MONTH\n if (vendor === 'nango_function_run') return Math.max(0, units) * NANGO_USD_PER_FUNCTION_RUN\n if (vendor === 'nango_proxy_request') return Math.max(0, units) * NANGO_USD_PER_PROXY_REQUEST\n if (vendor === 'nango_compute') return Math.max(0, units) * NANGO_USD_PER_COMPUTE_SEC\n return 0\n}\n","import type { CanonicalLocationName } from './types.js'\n\nexport const LOCATIONS: Record<string, CanonicalLocationName> = {\n 'austin': 'Austin,Texas,United States',\n 'new york': 'New York,New York,United States',\n 'new york city': 'New York,New York,United States',\n 'nyc': 'New York,New York,United States',\n 'los angeles': 'Los Angeles,California,United States',\n 'la': 'Los Angeles,California,United States',\n 'chicago': 'Chicago,Illinois,United States',\n 'houston': 'Houston,Texas,United States',\n 'phoenix': 'Phoenix,Arizona,United States',\n 'philadelphia': 'Philadelphia,Pennsylvania,United States',\n 'philly': 'Philadelphia,Pennsylvania,United States',\n 'san antonio': 'San Antonio,Texas,United States',\n 'dallas': 'Dallas,Texas,United States',\n 'miami': 'Miami,Florida,United States',\n 'seattle': 'Seattle,Washington,United States',\n 'denver': 'Denver,Colorado,United States',\n 'loveland': 'Loveland,Colorado,United States',\n 'loveland co': 'Loveland,Colorado,United States',\n 'fort collins': 'Fort Collins,Colorado,United States',\n 'boulder': 'Boulder,Colorado,United States',\n 'colorado springs': 'Colorado Springs,Colorado,United States',\n 'boston': 'Boston,Massachusetts,United States',\n 'atlanta': 'Atlanta,Georgia,United States',\n 'san francisco': 'San Francisco,California,United States',\n 'sf': 'San Francisco,California,United States',\n 'portland': 'Portland,Oregon,United States',\n 'las vegas': 'Las Vegas,Nevada,United States',\n 'minneapolis': 'Minneapolis,Minnesota,United States',\n 'detroit': 'Detroit,Michigan,United States',\n 'nashville': 'Nashville,Tennessee,United States',\n 'charlotte': 'Charlotte,North Carolina,United States',\n 'orlando': 'Orlando,Florida,United States',\n 'san diego': 'San Diego,California,United States',\n 'baltimore': 'Baltimore,Maryland,United States',\n 'sacramento': 'Sacramento,California,United States',\n 'columbus': 'Columbus,Ohio,United States',\n 'indianapolis': 'Indianapolis,Indiana,United States',\n 'san jose': 'San Jose,California,United States',\n 'fort worth': 'Fort Worth,Texas,United States',\n 'jacksonville': 'Jacksonville,Florida,United States',\n 'memphis': 'Memphis,Tennessee,United States',\n 'louisville': 'Louisville,Kentucky,United States',\n 'raleigh': 'Raleigh,North Carolina,United States',\n 'richmond': 'Richmond,Virginia,United States',\n 'salt lake city': 'Salt Lake City,Utah,United States',\n 'toronto': 'Toronto,Ontario,Canada',\n 'vancouver': 'Vancouver,British Columbia,Canada',\n 'montreal': 'Montreal,Quebec,Canada',\n 'calgary': 'Calgary,Alberta,Canada',\n 'ottawa': 'Ottawa,Ontario,Canada',\n 'london': 'London,England,United Kingdom',\n 'manchester': 'Manchester,England,United Kingdom',\n 'birmingham': 'Birmingham,England,United Kingdom',\n 'edinburgh': 'Edinburgh,Scotland,United Kingdom',\n 'glasgow': 'Glasgow,Scotland,United Kingdom',\n 'leeds': 'Leeds,England,United Kingdom',\n 'sydney': 'Sydney,New South Wales,Australia',\n 'melbourne': 'Melbourne,Victoria,Australia',\n 'brisbane': 'Brisbane,Queensland,Australia',\n 'perth': 'Perth,Western Australia,Australia',\n 'adelaide': 'Adelaide,South Australia,Australia',\n 'dublin': 'Dublin,Leinster,Ireland',\n}\n","import type { CanonicalLocationName, UuleString } from './types.js'\nimport { LOCATIONS } from './locations.js'\n\nfunction encodeVarint(value: number): number[] {\n const bytes: number[] = []\n let remaining = value\n do {\n let byte = remaining & 0x7f\n remaining >>>= 7\n if (remaining > 0) byte |= 0x80\n bytes.push(byte)\n } while (remaining > 0)\n return bytes\n}\n\nexport function encodeUule(name: CanonicalLocationName): UuleString {\n const locationBytes = Buffer.from(name, 'utf8')\n const payload = Buffer.concat([\n Buffer.from([0x08, 0x02, 0x10, 0x20, 0x22]),\n Buffer.from(encodeVarint(locationBytes.length)),\n locationBytes,\n ])\n return `w+${payload.toString('base64')}`\n}\n\nexport function normalizeLocation(input: string): CanonicalLocationName {\n const raw = input.toLowerCase().trim()\n if (LOCATIONS[raw]) return LOCATIONS[raw] as CanonicalLocationName\n const beforeComma = raw.split(',')[0].trim()\n if (beforeComma !== raw && LOCATIONS[beforeComma]) return LOCATIONS[beforeComma] as CanonicalLocationName\n const withoutState = raw.replace(/\\s+[a-z]{2}$/, '').trim()\n if (withoutState !== raw && LOCATIONS[withoutState]) return LOCATIONS[withoutState] as CanonicalLocationName\n return input as CanonicalLocationName\n}\n","import type { CanonicalLocationName } from './types.js'\n\nfunction normalizedTerms(value: string): string[] {\n return value\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter(Boolean)\n}\n\nfunction includesTermSequence(query: string, phrase: string): boolean {\n const queryTerms = normalizedTerms(query)\n const phraseTerms = normalizedTerms(phrase)\n if (phraseTerms.length === 0 || phraseTerms.length > queryTerms.length) return false\n\n return queryTerms.some((_, start) => phraseTerms.every((term, index) => queryTerms[start + index] === term))\n}\n\nfunction cityFromCanonicalLocation(location: CanonicalLocationName, fallback: string): string {\n return location.split(',', 1)[0]?.trim() || fallback.split(',', 1)[0]?.trim() || fallback.trim()\n}\n\n/**\n * Makes a location request visible to Google without making callers duplicate\n * a city that is already in their query. UULE carries the same location signal\n * separately; this function only determines the search text.\n */\nexport function buildLocalizedSerpQuery(\n query: string,\n location: string | undefined,\n canonicalLocation: CanonicalLocationName | null,\n): string {\n const normalizedQuery = query.trim()\n const requestedLocation = location?.trim()\n if (!requestedLocation || !canonicalLocation) return normalizedQuery\n\n const city = cityFromCanonicalLocation(canonicalLocation, requestedLocation)\n return includesTermSequence(normalizedQuery, city)\n ? normalizedQuery\n : `${normalizedQuery} ${requestedLocation}`\n}\n","import type { CanonicalLocationName, LocalPackBusiness, OrganicResult, SerpLocationCandidate, SerpLocationEvidence } from './types.js'\n\nconst STATE_TO_CODE: Record<string, string> = {\n alabama: 'AL', alaska: 'AK', arizona: 'AZ', arkansas: 'AR', california: 'CA',\n colorado: 'CO', connecticut: 'CT', delaware: 'DE', florida: 'FL', georgia: 'GA',\n hawaii: 'HI', idaho: 'ID', illinois: 'IL', indiana: 'IN', iowa: 'IA',\n kansas: 'KS', kentucky: 'KY', louisiana: 'LA', maine: 'ME', maryland: 'MD',\n massachusetts: 'MA', michigan: 'MI', minnesota: 'MN', mississippi: 'MS', missouri: 'MO',\n montana: 'MT', nebraska: 'NE', nevada: 'NV', 'new hampshire': 'NH', 'new jersey': 'NJ',\n 'new mexico': 'NM', 'new york': 'NY', 'north carolina': 'NC', 'north dakota': 'ND', ohio: 'OH',\n oklahoma: 'OK', oregon: 'OR', pennsylvania: 'PA', 'rhode island': 'RI', 'south carolina': 'SC',\n 'south dakota': 'SD', tennessee: 'TN', texas: 'TX', utah: 'UT', vermont: 'VT',\n virginia: 'VA', washington: 'WA', 'west virginia': 'WV', wisconsin: 'WI', wyoming: 'WY',\n 'district of columbia': 'DC',\n}\n\nconst STATE_PATTERN = [\n ...Object.keys(STATE_TO_CODE).map(s => s.replace(/\\s+/g, '\\\\s+')),\n ...Object.values(STATE_TO_CODE),\n].join('|')\n\nconst CITY_STATE_RE = new RegExp(`\\\\b([A-Z][A-Za-z]+(?:[\\\\s.-][A-Z][A-Za-z]+){0,4}),?\\\\s+(${STATE_PATTERN})\\\\b`, 'gi')\n\nfunction normalizeRegionCode(input: string | null | undefined): string | null {\n if (!input) return null\n const trimmed = input.trim()\n if (/^[A-Z]{2}$/i.test(trimmed)) return trimmed.toUpperCase()\n return STATE_TO_CODE[trimmed.toLowerCase()] ?? null\n}\n\nfunction normalizeCity(input: string): string {\n const cleaned = input\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^.*\\b(?:in|near|around|serving)\\s+/i, '')\n return cleaned.toLowerCase().replace(/\\b[a-z]/g, char => char.toUpperCase())\n}\n\nfunction parseExpected(canonicalLocation: CanonicalLocationName | null): SerpLocationEvidence['expected'] {\n if (!canonicalLocation) return null\n const [city = '', region = ''] = canonicalLocation.split(',').map(part => part.trim())\n return {\n city: normalizeCity(city),\n regionCode: normalizeRegionCode(region),\n canonicalLocation,\n }\n}\n\nfunction addCandidate(\n candidates: Map<string, SerpLocationCandidate>,\n city: string,\n region: string,\n example: string,\n): void {\n const normalizedCity = normalizeCity(city)\n const regionCode = normalizeRegionCode(region)\n if (!normalizedCity || !regionCode) return\n const key = `${normalizedCity.toLowerCase()}|${regionCode}`\n const existing = candidates.get(key)\n if (existing) {\n existing.count++\n if (existing.examples.length < 3 && !existing.examples.includes(example)) existing.examples.push(example)\n return\n }\n candidates.set(key, { city: normalizedCity, regionCode, count: 1, examples: [example] })\n}\n\nfunction decodeSerpText(text: string): string {\n try {\n return decodeURIComponent(text)\n } catch {}\n\n try {\n return decodeURIComponent(text.replace(/%(?![0-9a-fA-F]{2})/g, '%25'))\n } catch {\n return text\n }\n}\n\nfunction scanText(candidates: Map<string, SerpLocationCandidate>, text: string): void {\n const normalized = decodeSerpText(text).replace(/[+/|_-]+/g, ' ')\n for (const match of normalized.matchAll(CITY_STATE_RE)) {\n addCandidate(candidates, match[1] ?? '', match[2] ?? '', normalized.slice(0, 180))\n }\n}\n\nexport function inferSerpLocationEvidence(\n canonicalLocation: CanonicalLocationName | null,\n organicResults: OrganicResult[],\n localPack: LocalPackBusiness[],\n): SerpLocationEvidence {\n const expected = parseExpected(canonicalLocation)\n const candidates = new Map<string, SerpLocationCandidate>()\n\n for (const result of organicResults) {\n scanText(candidates, [result.title, result.snippet ?? '', result.cite ?? '', result.url].join(' '))\n }\n for (const business of localPack) {\n scanText(candidates, [business.name, ...business.metadata, business.websiteUrl ?? '', business.directionsUrl ?? ''].join(' '))\n }\n\n const rankedCandidates = Array.from(candidates.values())\n .sort((a, b) => b.count - a.count || a.city.localeCompare(b.city))\n .slice(0, 8)\n\n if (!expected) {\n return { status: 'not_requested', expected: null, candidates: rankedCandidates }\n }\n if (rankedCandidates.length === 0) {\n return { status: 'unknown', expected, candidates: [] }\n }\n\n const matched = rankedCandidates.some(candidate =>\n candidate.city.toLowerCase() === expected.city.toLowerCase()\n && (expected.regionCode == null || candidate.regionCode === expected.regionCode)\n )\n\n return {\n status: matched ? 'matched' : 'mismatch',\n expected,\n candidates: rankedCandidates,\n }\n}\n","const MAX_ANSWER_LENGTH = 1200\n\nconst BOILERPLATE_PATTERNS: RegExp[] = [\n /An AI Overview is not available for this search/gi,\n /Can't generate an AI overview right now\\.?\\s*Try again later\\.?/gi,\n /\\bAI Overview\\b/gi,\n /\\bView all\\b/gi,\n]\n\nconst CUT_MARKERS: RegExp[] = [\n /\\bRelated Links\\b/i,\n /\\bAsk anything in\\s*AI Mode\\b/i,\n /\\bAI can make mistakes\\b/i,\n /\\bThis is for informational purposes only\\b/i,\n /\\bShow more\\b/i,\n /\\b\\d+\\s+sites\\b/i,\n /\\b\\d{1,2}\\s*[msh]\\s*[A-Z][A-Za-z]/,\n /\\b(?:YouTube|Reddit|Facebook|Instagram|TikTok)·/,\n]\n\nfunction normalizeWhitespace(text: string): string {\n return text\n .replace(/\\u00a0/g, ' ')\n .replace(/([.!?])([A-Z])/g, '$1 $2')\n .replace(/([:;])([A-Z])/g, '$1 $2')\n .replace(/([a-z])([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([A-Z][a-z])/g, '$1 $2')\n .replace(/([a-z])(\\d)/g, '$1 $2')\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\nfunction cutAtFirstMarker(text: string): string {\n let cutAt = -1\n for (const marker of CUT_MARKERS) {\n const match = marker.exec(text)\n marker.lastIndex = 0\n if (match && (cutAt === -1 || match.index < cutAt)) cutAt = match.index\n }\n return cutAt === -1 ? text : text.slice(0, cutAt)\n}\n\nfunction cutAtSourceTitle(text: string, sourceTitle?: string): string {\n const title = sourceTitle?.trim()\n if (!title || title.length < 8) return text\n const idx = text.toLowerCase().indexOf(title.toLowerCase())\n return idx > 40 ? text.slice(0, idx) : text\n}\n\nfunction findAttributionCut(beforeUrl: string): number {\n const dateMatch = beforeUrl.match(/[•·]\\s*(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+\\d{1,2},\\s+\\d{4}/i)\n if (dateMatch?.index && dateMatch.index > 40) return dateMatch.index\n\n const start = Math.max(0, beforeUrl.length - 260)\n const tail = beforeUrl.slice(start)\n const sentenceBreaks = [...tail.matchAll(/[.!?]\\s*(?=[A-Z][A-Za-z0-9\"'$])/g)]\n for (const match of sentenceBreaks) {\n const remainder = tail.slice(match.index + 1).trim()\n const lead = remainder.slice(0, 160)\n const looksLikeTitle = /^(?:Best|Top|What|How|Why|When|Where|Which|Can|Should|Is|Are|Do|Does)\\b/i.test(remainder)\n if (\n remainder.length > 20 &&\n looksLikeTitle &&\n /(?:\\s[-|]\\s|Heating|Cooling|Company|Services|Blog|Guide|Review)/i.test(lead)\n ) {\n return start + match.index + 1\n }\n }\n const last = sentenceBreaks.at(-1)\n if (last?.index !== undefined) return start + last.index + 1\n\n return beforeUrl.length\n}\n\nfunction cutAtUrlAttribution(text: string): string {\n const urlMatch = text.match(/https?:\\/\\/\\S+/i)\n if (!urlMatch?.index) return text\n\n const beforeUrl = text.slice(0, urlMatch.index)\n return beforeUrl.slice(0, findAttributionCut(beforeUrl))\n}\n\nfunction trimToSentenceLimit(text: string): string {\n if (text.length <= MAX_ANSWER_LENGTH) return text\n const slice = text.slice(0, MAX_ANSWER_LENGTH)\n const lastSentence = Math.max(slice.lastIndexOf('.'), slice.lastIndexOf('!'), slice.lastIndexOf('?'))\n return (lastSentence > 240 ? slice.slice(0, lastSentence + 1) : slice).trim()\n}\n\nexport function cleanPAAAnswerText(\n answer: string | null | undefined,\n question?: string,\n sourceTitle?: string,\n): string | undefined {\n if (!answer) return undefined\n\n let text = normalizeWhitespace(answer)\n const normalizedQuestion = question ? normalizeWhitespace(question) : ''\n if (normalizedQuestion && text.toLowerCase().startsWith(normalizedQuestion.toLowerCase())) {\n text = text.slice(normalizedQuestion.length).trim()\n }\n\n if (/^An error has occurred\\.?\\s*Please try again later\\.?/i.test(text)) {\n return undefined\n }\n\n for (const pattern of BOILERPLATE_PATTERNS) {\n text = text.replace(pattern, ' ')\n }\n text = text\n .replace(/\\b[A-Z][A-Za-z&'\\u2019 -]{2,60}\\+\\d+\\b/g, ' ')\n .replace(/\\b(?:[a-z0-9-]+\\.)+[a-z]{2,}\\+\\d+\\b/gi, ' ')\n\n text = normalizeWhitespace(text)\n text = cutAtFirstMarker(text)\n text = cutAtSourceTitle(text, sourceTitle)\n text = cutAtUrlAttribution(text)\n text = normalizeWhitespace(text)\n text = text.replace(/\\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+\\d{1,2},\\s+\\d{4}$/i, '').trim()\n text = trimToSentenceLimit(text)\n\n if (!text) return undefined\n if (/^An error has occurred\\.?\\s*Please try again later\\.?$/i.test(text)) return undefined\n\n return text\n}\n","export interface AISurfaceSelectorConfig {\n aio: {\n root: string\n legacyRoot?: string\n wrapper?: string\n controller: string\n contentSubtree: string\n heading: string\n header: string\n showMoreButton: string\n sourcesPanel: string\n disclaimer: string\n }\n aim: {\n root: string\n wrapper: string\n }\n expandWaitMs?: number\n}\n\nexport async function extractAISurfacesFromDocument(config?: AISurfaceSelectorConfig): Promise<{\n surface: 'web' | 'aim' | 'unknown'\n aiOverview: {\n detected: boolean\n text: string | null\n citations: Array<{ text: string; href: string }>\n expanded: boolean\n fullyExpanded: boolean\n sections: string[]\n }\n aiMode: {\n detected: boolean\n text: string | null\n citations: Array<{ text: string; href: string }>\n }\n}> {\n const selectors = config ?? {\n aio: {\n root: '[data-lhcontainer][data-streaming-container][eid]',\n legacyRoot: '[data-hveid=\"CBMQAA\"]',\n wrapper: '.Fgyi2e',\n controller: '[jscontroller=\"AkrxPe\"]',\n contentSubtree: '[data-subtree=\"mfc\"]',\n heading: '.Fzsovc.cwYVJe.RJPOee',\n header: '.heWuVc',\n showMoreButton: '[aria-label=\"Show more AI Overview\"]',\n sourcesPanel: '.OZ9ddf.WAUd4',\n disclaimer: '.DuQANe.MSJHRb',\n },\n aim: {\n root: '[data-hveid=\"CAUQAA\"]',\n wrapper: '.Fgyi2e',\n },\n expandWaitMs: 1500,\n }\n\n const sn: string = (window as unknown as { google?: { sn?: string } }).google?.sn ?? 'unknown'\n const surface: 'web' | 'aim' | 'unknown' =\n sn === 'aim' ? 'aim' : sn === 'web' ? 'web' : 'unknown'\n\n function textOf(el: Element | null): string {\n if (!el) return ''\n return ((el as HTMLElement).innerText ?? el.textContent ?? '').trim()\n }\n\n function hasSubstantiveContent(text: string | null): text is string {\n if (!text) return false\n const normalized = text.replace(/\\s+/g, ' ').trim()\n return normalized.length >= 16 && normalized.split(' ').filter(Boolean).length >= 2\n }\n\n function hasAIOverviewLabel(el: Element): boolean {\n const heading = el.querySelector(selectors.aio.heading)\n if (textOf(heading) === 'AI Overview') return true\n const header = el.querySelector(selectors.aio.header)\n if (textOf(header).split(/\\n|\\s{2,}/).some(part => part.trim() === 'AI Overview')) return true\n return textOf(el).includes('AI Overview')\n }\n\n function findAIORoot(): Element | null {\n const primaryRoots = Array.from(document.querySelectorAll(selectors.aio.root))\n const labeledPrimary = primaryRoots.find(hasAIOverviewLabel)\n if (labeledPrimary) return labeledPrimary\n if (primaryRoots.length > 0) return primaryRoots[0]\n\n if (selectors.aio.legacyRoot) {\n const legacy = document.querySelector(selectors.aio.legacyRoot)\n if (legacy) return legacy\n }\n\n const headings = document.querySelectorAll(`${selectors.aio.heading}, h1, h2, h3, [role=\"heading\"]`)\n for (const h of headings) {\n if (textOf(h) !== 'AI Overview') continue\n let el: Element | null = h.parentElement\n for (let i = 0; i < 8 && el; i++) {\n if (\n el.matches(selectors.aio.root) ||\n el.querySelector(selectors.aio.controller) ||\n el.querySelector(selectors.aio.contentSubtree)\n ) {\n return el\n }\n el = el.parentElement\n }\n return h.parentElement\n }\n return null\n }\n\n function cleanText(target: Element | null): string | null {\n if (!target) return null\n const clone = target.cloneNode(true) as Element\n clone.querySelectorAll([\n 'script',\n 'style',\n 'noscript',\n 'img',\n 'picture',\n 'video',\n selectors.aio.header,\n selectors.aio.showMoreButton,\n selectors.aio.sourcesPanel,\n selectors.aio.disclaimer,\n '[data-subtree=\"dfa\"]',\n '[data-src-id]',\n '[role=\"dialog\"]',\n '.HWMcu',\n '.bTFeG',\n '.CyMdWb',\n '.MFrAxb',\n '.F0OfWd.hfWAgb',\n '.x2qcTc.fZavHb',\n '.SvjEff',\n '.sR2MY',\n '.lKuDef',\n '.GSPQcc',\n 'a[href]',\n 'button',\n '[role=\"button\"]',\n ].join(',')).forEach(el => el.remove())\n\n const holder = document.createElement('div')\n holder.style.position = 'fixed'\n holder.style.left = '-10000px'\n holder.style.top = '0'\n holder.style.width = `${Math.max(320, Math.round((target as HTMLElement).getBoundingClientRect?.().width || 960))}px`\n holder.style.opacity = '0'\n holder.style.pointerEvents = 'none'\n holder.append(clone)\n document.body.append(holder)\n\n const rendered = ((clone as HTMLElement).innerText || clone.textContent || '')\n holder.remove()\n\n const lines = rendered\n .replace(/\\r/g, '')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n[ \\t]+/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/[ \\t]{2,}/g, ' ')\n .trim()\n .split('\\n')\n .map(line => line.replace(/\\u00a0/g, ' ').trim())\n .filter(Boolean)\n\n const filteredLines: string[] = []\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n const next = lines[i + 1] ?? ''\n if (line === 'AI Overview') continue\n if (line === 'Show more') continue\n if (/^AI can make mistakes/i.test(line)) continue\n if (/^Thank you\\b/i.test(line)) continue\n if (/^Your feedback helps Google improve/i.test(line)) continue\n if (/^\\+?\\d+$/.test(line)) continue\n if (/^\\+\\d+$/.test(next) && line.length <= 80) {\n i++\n continue\n }\n filteredLines.push(line)\n }\n\n const raw = filteredLines\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n\n if (!raw || /not available|try again|can't generate/i.test(raw)) return null\n return raw\n }\n\n function normalizeHref(rawHref: string): string | null {\n if (!rawHref || rawHref.startsWith('javascript:')) return null\n let href = rawHref\n try {\n const absolute = new URL(rawHref, window.location.href)\n const q = absolute.searchParams.get('q') ?? absolute.searchParams.get('url')\n if (/(\\.|^)google\\./i.test(absolute.hostname) && q?.startsWith('http')) {\n href = q\n } else {\n href = absolute.href\n }\n } catch {\n return null\n }\n\n if (!/^https?:\\/\\//i.test(href)) return null\n try {\n const url = new URL(href)\n const isGoogleInternal = /(\\.|^)google\\./i.test(url.hostname)\n if (isGoogleInternal) return null\n return url.href\n } catch {\n return null\n }\n }\n\n function extractCitations(root: Element | null): Array<{ text: string; href: string }> {\n if (!root) return []\n const seen = new Set<string>()\n const citations: Array<{ text: string; href: string }> = []\n\n for (const a of Array.from(root.querySelectorAll('a[href]'))) {\n const href = normalizeHref(a.getAttribute('href') ?? '')\n if (!href || seen.has(href)) continue\n seen.add(href)\n\n let fallbackHost = ''\n try {\n fallbackHost = new URL(href).hostname.replace(/^www\\./, '')\n } catch {}\n\n citations.push({\n text: textOf(a) || fallbackHost || href,\n href,\n })\n }\n\n return citations\n }\n\n async function maybeExpand(root: Element): Promise<boolean> {\n const button = root.querySelector(selectors.aio.showMoreButton)\n if (!button || button.getAttribute('aria-expanded') !== 'false') return false\n ;(button as HTMLElement).click()\n const waitMs = selectors.expandWaitMs ?? 1500\n if (waitMs > 0) await new Promise(resolve => setTimeout(resolve, waitMs))\n return true\n }\n\n const aioRoot = findAIORoot()\n let aioText: string | null = null\n let aioCitations: Array<{ text: string; href: string }> = []\n let aioExpanded = false\n let aioFullyExpanded = false\n let aioSections: string[] = []\n\n if (aioRoot) {\n aioExpanded = await maybeExpand(aioRoot)\n const controller = aioRoot.querySelector(selectors.aio.controller)\n const contentSubtree = aioRoot.querySelector(selectors.aio.contentSubtree)\n const showMore = aioRoot.querySelector(selectors.aio.showMoreButton)\n aioFullyExpanded =\n controller?.getAttribute('data-trnct') === 'false' ||\n showMore?.getAttribute('aria-expanded') === 'true' ||\n !showMore\n\n aioText = cleanText(contentSubtree ?? controller ?? aioRoot)\n aioSections = (aioText ?? '')\n .split('\\n')\n .map(line => line.trim())\n .filter(line => /^\\d+\\.\\s+.+/.test(line))\n aioCitations = extractCitations(aioRoot)\n }\n\n const aimRoot = document.querySelector(selectors.aim.root)\n const aimRootDetected = surface === 'aim' && !!aimRoot\n const aimContainer = aimRoot?.closest(selectors.aim.wrapper) ?? aimRoot\n const rawAimText = aimRootDetected ? cleanText(aimContainer) : null\n const aimDetected = aimRootDetected && hasSubstantiveContent(rawAimText)\n const aimText = aimDetected ? rawAimText : null\n const aimCitations = aimDetected ? extractCitations(aimContainer) : []\n\n const aiOverviewDetected = !!aioRoot && hasSubstantiveContent(aioText)\n\n return {\n surface,\n aiOverview: {\n detected: aiOverviewDetected,\n text: aiOverviewDetected ? aioText : null,\n citations: aiOverviewDetected ? aioCitations : [],\n expanded: aiOverviewDetected ? aioExpanded : false,\n fullyExpanded: aiOverviewDetected ? aioFullyExpanded : false,\n sections: aiOverviewDetected ? aioSections : [],\n },\n aiMode: {\n detected: aimDetected && aimText !== null,\n text: aimText,\n citations: aimCitations,\n },\n }\n}\n","import type { Page } from 'playwright'\nimport type { IBrowserDriver } from '../driver/IBrowserDriver.js'\nimport type { IProgressReporter } from '../output/IProgressReporter.js'\nimport { PAASelectors, VideoSelectors, ForumSelectors, ShortVideoSelectors, AIOverviewSelectors, AIModeSelectors, WhatPeopleSayingSelectors, OrganicSelectors, LocalPackSelectors } from '../selectors.js'\nimport { encodeUule, normalizeLocation } from '../uule.js'\nimport { buildLocalizedSerpQuery } from '../serp-location-query.js'\nimport { inferSerpLocationEvidence } from '../serp-location-debug.js'\nimport { RawPAAItemSchema, recencyToTbs } from '../schemas.js'\nimport { cleanPAAAnswerText } from '../lib/paa-answer-cleanup.js'\nimport { CaptchaError, RequestAbortedError } from '../errors.js'\nimport { extractAISurfacesFromDocument } from './ai-surfaces.js'\nimport type {\n HarvestOptions, HarvestResult, HarvestStats,\n RawPAAItem, PAANode, FlatRow, NormalizedQuestionKey, DriverConfig,\n VideoResult, ForumResult, AIOverviewResult, AIModeResult, GoogleSurface,\n WhatPeopleSayingCard, EntityIds, EntityRecord, HarvestDiagnosticWarning, HarvestCompleteness,\n OrganicResult, LocalPackBusiness, BrowserDebugSnapshot, HarvestDebugSnapshot, SerpLocationEvidence,\n} from '../types.js'\nimport type { IPAAExtractor } from './IPAAExtractor.js'\n\nconst DESKTOP_USER_AGENT =\n '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'\n\nconst MOBILE_USER_AGENT =\n 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1'\n\nexport class PAAExtractor implements IPAAExtractor {\n constructor(\n private readonly driver: IBrowserDriver,\n private readonly reporter: IProgressReporter,\n ) {}\n\n private completeness: HarvestCompleteness = {\n paaWithoutAnswer: 0,\n paaWithoutSource: 0,\n paaAnswersRecovered: 0,\n aioShareCaptured: null,\n }\n\n private aioShareUrlEarly: string | null = null\n private collectedRows: FlatRow[] = []\n private partialContext: { seed: string; location: string | null; startMs: number } | null = null\n\n private normalizeQuestion(q: string): NormalizedQuestionKey {\n return q.toLowerCase().replace(/[^\\w\\s]/g, '').replace(/\\s+/g, ' ').trim() as NormalizedQuestionKey\n }\n\n private throwIfAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) return\n if (signal.reason instanceof DOMException && signal.reason.name === 'TimeoutError') throw signal.reason\n throw new RequestAbortedError()\n }\n\n private async throwIfCaptcha(page: Page, context: string): Promise<void> {\n const captchaCount = await page.locator(PAASelectors.captchaMarker).count().catch(() => 0)\n if (captchaCount > 0) {\n throw new CaptchaError(`${context} returned a CAPTCHA — retrying with a fresh session.`)\n }\n }\n\n private async extractVisibleItems(page: Page): Promise<RawPAAItem[]> {\n const sels = PAASelectors\n const raw = await page.evaluate((selectors) => {\n function cleanText(el: Element | null): string {\n if (!el) return ''\n const parts: string[] = []\n for (const n of el.childNodes) {\n if (n.nodeType === Node.TEXT_NODE) {\n const text = n.textContent?.trim()\n if (text) parts.push(text)\n } else if (\n (n as Element).tagName === 'STYLE' ||\n (n as Element).tagName === 'SCRIPT'\n ) {\n continue\n } else {\n const text = cleanText(n as Element)\n if (text) parts.push(text)\n }\n }\n return parts.join(' ').replace(/\\s+/g, ' ').trim()\n }\n function cleanSourceLabel(value: string | null | undefined): string {\n return (value ?? '')\n .replace(/\\s*\\(\\+\\d+\\)\\s*-\\s*View related links.*$/i, '')\n .replace(/\\s*-\\s*Opens in new tab.*$/i, '')\n .trim()\n }\n return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {\n const clickTarget = pair.querySelector(selectors.clickTarget)\n const expanded = pair.classList.contains(selectors.expandedClass)\n || clickTarget?.getAttribute('aria-expanded') === 'true'\n const answerContainer = expanded\n ? pair.querySelector(selectors.answerContainer)\n : null\n const aiAnswerBody = answerContainer?.querySelector(selectors.aiAnswerBody)\n const answerElement = aiAnswerBody ?? answerContainer\n let sourceTitle = expanded\n ? (pair.querySelector(selectors.sourceTitle) as HTMLElement | null)?.innerText?.trim() || undefined\n : undefined\n let sourceSite = expanded\n ? (pair.querySelector(selectors.sourceSite) as HTMLElement | null)?.innerText?.trim() || undefined\n : undefined\n let sourceCite = expanded\n ? (pair.querySelector(selectors.sourceCite) as HTMLElement | null)?.innerText?.trim() || undefined\n : undefined\n if (expanded && !sourceCite) {\n const seenHrefs = new Set<string>()\n const anchors: Array<{ text: string; label: string; href: string; host: string }> = []\n const anchorRoot = answerElement ?? pair\n const prioritized = [\n ...Array.from(anchorRoot.querySelectorAll(selectors.aiCitation)),\n ...Array.from(anchorRoot.querySelectorAll(selectors.aiSourceCard)),\n ...Array.from(anchorRoot.querySelectorAll('a[href]')),\n ] as HTMLAnchorElement[]\n for (const a of prioritized) {\n const href = a.href\n if (!/^https?:\\/\\//.test(href) || seenHrefs.has(href)) continue\n let host = ''\n try { host = new URL(href).hostname } catch { continue }\n if (/(^|\\.)google(usercontent)?\\.[a-z.]+$/.test(host)) continue\n seenHrefs.add(href)\n anchors.push({\n text: (a.textContent || '').trim(),\n label: cleanSourceLabel(a.getAttribute('aria-label')),\n href,\n host,\n })\n }\n const primary = anchors.find(x => x.label.length > 2 || x.text.length > 2) ?? anchors[0]\n if (primary) {\n sourceTitle = sourceTitle || primary.label || primary.text || undefined\n sourceSite = sourceSite || primary.host.replace(/^www\\./, '')\n sourceCite = primary.href\n }\n }\n return {\n question:\n pair.getAttribute(selectors.itemDataQ) ||\n pair.getAttribute(selectors.itemDataInitQ) ||\n (pair.querySelector(selectors.itemQuestionEl) as HTMLElement | null)?.innerText?.trim() ||\n '',\n answer: cleanText(answerElement) || undefined,\n sourceTitle,\n sourceSite,\n sourceCite,\n }\n })\n }, sels)\n\n return raw.flatMap((item) => {\n const cleaned = {\n ...item,\n answer: cleanPAAAnswerText(item.answer, item.question, item.sourceTitle),\n }\n const result = RawPAAItemSchema.safeParse(cleaned)\n if (!result.success) {\n console.warn('[PAAExtractor] item parse failed:', item.question, result.error.issues[0]?.message)\n return []\n }\n return [result.data]\n })\n }\n\n private async clickItem(page: Page, questionText: string): Promise<void> {\n try {\n const pairLocator = page.locator(\n `${PAASelectors.item}[data-q=\"${questionText}\"], ${PAASelectors.item}[data-initq=\"${questionText}\"]`\n ).first()\n await pairLocator.click()\n } catch { }\n }\n\n private toFlatRow(item: RawPAAItem, depth: number, parentQuestion: string | null, seed: string): FlatRow {\n return {\n seed_query: seed,\n question: item.question,\n answer: item.answer ?? '',\n source_title: item.sourceTitle ?? '',\n source_site: item.sourceSite ?? '',\n source_cite: item.sourceCite ?? '',\n depth,\n parent_question: parentQuestion ?? '',\n extracted_at: new Date().toISOString(),\n }\n }\n\n getPartialResult(): HarvestResult | null {\n const ctx = this.partialContext\n if (!ctx || this.collectedRows.length === 0) return null\n const flat = [...this.collectedRows]\n const stats: HarvestStats = {\n seed: ctx.seed,\n totalQuestions: flat.length,\n maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),\n durationMs: Date.now() - ctx.startMs,\n errorCount: 0,\n }\n return {\n seed: ctx.seed,\n location: ctx.location,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'paa_found',\n problem: null,\n completeness: { ...this.completeness },\n warnings: [{\n code: 'paa_partial',\n surface: 'paa',\n message: `Harvest stopped before finishing; returning the ${flat.length} question(s) collected up to that point.`,\n retryable: true,\n }],\n },\n totalQuestions: flat.length,\n whatPeopleSaying: [],\n tree: this.buildTree(flat, ctx.seed),\n flat,\n videos: [],\n forums: [],\n organicResults: [],\n localPack: [],\n entityIds: {},\n stats,\n } as unknown as HarvestResult\n }\n\n private async runBFS(page: Page, options: HarvestOptions, signal?: AbortSignal): Promise<FlatRow[]> {\n const seenKeys = new Set<NormalizedQuestionKey>()\n const seenQs = new Set<string>()\n const orderedQs: string[] = []\n this.collectedRows = []\n const results: FlatRow[] = this.collectedRows\n const abandonedNeverReclick = new Set<string>()\n const clickedOnceEver = new Set<string>()\n\n const itemArgs = {\n sel: PAASelectors.item,\n dataQ: PAASelectors.itemDataQ,\n dataInitQ: PAASelectors.itemDataInitQ,\n questionEl: PAASelectors.itemQuestionEl,\n answerSel: PAASelectors.answerContainer,\n expandedClass: PAASelectors.expandedClass,\n clickTarget: PAASelectors.clickTarget,\n }\n\n const readItemStates = (): Promise<Array<{ q: string; done: boolean }>> =>\n page.evaluate(\n ({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }: typeof itemArgs) => {\n const answerText = (root: Element | null): string => {\n if (!root) return ''\n let out = ''\n const walk = (n: Node): void => {\n for (const c of Array.from(n.childNodes)) {\n if (c.nodeType === Node.TEXT_NODE) out += c.textContent || ''\n else if (c.nodeType === Node.ELEMENT_NODE && (c as Element).tagName !== 'STYLE' && (c as Element).tagName !== 'SCRIPT') walk(c)\n }\n }\n walk(root)\n return out.replace(/\\s+/g, ' ').trim()\n }\n return Array.from(document.querySelectorAll(sel))\n .map(el => ({\n q:\n el.getAttribute(dataQ) ||\n el.getAttribute(dataInitQ) ||\n (el.querySelector(questionEl) as HTMLElement | null)?.innerText?.trim() ||\n '',\n done: el.classList.contains(expandedClass) || answerText(el.querySelector(answerSel)).length > 30,\n }))\n .filter(s => s.q)\n },\n itemArgs,\n )\n\n const expandOneItemSerially = async (q: string): Promise<'ok' | 'failed'> => {\n const clicked = await page.evaluate(\n ({ sel, dataQ, dataInitQ, clickTarget, q: target }: typeof itemArgs & { q: string }) => {\n const item = Array.from(document.querySelectorAll(sel)).find(el =>\n (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target)\n const btn = item?.querySelector(clickTarget) as HTMLElement | null\n if (!btn) return false\n btn.scrollIntoView({ block: 'center' })\n btn.click()\n return true\n },\n { ...itemArgs, q },\n ).catch(() => false)\n if (!clicked) return 'failed'\n try {\n const outcome = await page.waitForFunction(\n ({ sel, dataQ, dataInitQ, answerSel, q: target }: typeof itemArgs & { q: string }) => {\n const answerText = (root: Element | null): string => {\n if (!root) return ''\n let out = ''\n const walk = (n: Node): void => {\n for (const c of Array.from(n.childNodes)) {\n if (c.nodeType === Node.TEXT_NODE) out += c.textContent || ''\n else if (c.nodeType === Node.ELEMENT_NODE && (c as Element).tagName !== 'STYLE' && (c as Element).tagName !== 'SCRIPT') walk(c)\n }\n }\n walk(root)\n return out.replace(/\\s+/g, ' ').trim()\n }\n const item = Array.from(document.querySelectorAll(sel)).find(el =>\n (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target)\n if (!item) return 'gone'\n const text = answerText(item.querySelector(answerSel))\n if (/an error has occurred/i.test(text.slice(0, 80))) return 'error'\n if (text.length > 30) return 'ok'\n return false\n },\n { ...itemArgs, q },\n { timeout: 6500, polling: 200 },\n )\n return (await outcome.jsonValue()) === 'ok' ? 'ok' : 'failed'\n } catch {\n return 'failed'\n }\n }\n\n let round = 0\n let growthWaits = 0\n while (true) {\n if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break\n this.throwIfAborted(signal)\n\n const states = await readItemStates()\n for (const s of states) {\n if (!seenQs.has(s.q)) { seenQs.add(s.q); orderedQs.push(s.q) }\n }\n\n const kept = new Set(orderedQs.slice(0, options.maxQuestions))\n const clickable = states.filter(s => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q))\n const target = clickable.find(s => !s.done)\n ?? (seenQs.size < options.maxQuestions ? clickable[0] : undefined)\n if (!target) {\n if (seenQs.size >= options.maxQuestions) break\n if (++growthWaits > 1) break\n const grew = await page.waitForFunction(\n ({ sel, min }: { sel: string; min: number }) => document.querySelectorAll(sel).length > min,\n { sel: PAASelectors.item, min: states.length },\n { timeout: 3000 },\n ).catch(() => null)\n if (!grew) break\n continue\n }\n growthWaits = 0\n\n this.reporter.onDepth(++round)\n await this.throwIfCaptcha(page, 'Google PAA expansion')\n clickedOnceEver.add(target.q)\n if ((await expandOneItemSerially(target.q)) === 'failed') abandonedNeverReclick.add(target.q)\n }\n\n const itemMap = new Map((await this.extractVisibleItems(page)).map(i => [i.question, i]))\n await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick)\n\n for (const q of orderedQs) {\n if (results.length >= options.maxQuestions) break\n const key = this.normalizeQuestion(q)\n if (seenKeys.has(key)) continue\n seenKeys.add(key)\n const item = itemMap.get(q)\n if (item) {\n results.push(this.toFlatRow(item, 1, null, options.query))\n this.reporter.onQuestion({ question: item.question, answer: item.answer ?? null, sourceTitle: item.sourceTitle ?? null, sourceSite: item.sourceSite ?? null, sourceCite: item.sourceCite ?? null, depth: 1, parentQuestion: null, children: [] })\n } else {\n results.push(this.toFlatRow({ question: q, answer: undefined, sourceTitle: undefined, sourceSite: undefined, sourceCite: undefined }, 1, null, options.query))\n }\n }\n\n this.completeness.paaWithoutAnswer = results.filter(r => !r.answer).length\n this.completeness.paaWithoutSource = results.filter(r => !r.source_title && !r.source_site && !r.source_cite).length\n\n return results\n }\n\n private async fillIncompleteItems(\n page: Page,\n orderedQs: string[],\n itemMap: Map<string, RawPAAItem>,\n options: HarvestOptions,\n abandonedNeverReclick?: Set<string>,\n ): Promise<void> {\n const kept = new Set(orderedQs.slice(0, options.maxQuestions))\n const missingBefore = new Set(\n [...kept].filter(q => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q)),\n )\n if (missingBefore.size === 0) return\n\n for (let round = 0; round < 2; round++) {\n if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break\n const stillMissing = [...missingBefore].filter(q => !itemMap.get(q)?.answer)\n if (stillMissing.length === 0) break\n\n const reExpanded = await this.reExpandCollapsedItems(page).catch(() => false)\n await page.waitForTimeout(reExpanded ? 900 : 500)\n\n let improved = false\n for (const item of await this.extractVisibleItems(page)) {\n if (!kept.has(item.question)) continue\n const existing = itemMap.get(item.question)\n if (!item.answer || existing?.answer) continue\n itemMap.set(item.question, {\n ...item,\n sourceTitle: item.sourceTitle ?? existing?.sourceTitle,\n sourceSite: item.sourceSite ?? existing?.sourceSite,\n sourceCite: item.sourceCite ?? existing?.sourceCite,\n })\n improved = true\n }\n if (!improved) break\n }\n\n this.completeness.paaAnswersRecovered = [...missingBefore].filter(q => itemMap.get(q)?.answer).length\n }\n\n private async reExpandCollapsedItems(page: Page): Promise<boolean> {\n const unexpandedSel = `${PAASelectors.item}:not(.${PAASelectors.expandedClass}) ${PAASelectors.clickTarget}`\n const count = await page.locator(unexpandedSel).count().catch(() => 0)\n if (count === 0) return false\n for (let i = 0; i < count; i++) {\n try {\n const btn = page.locator(unexpandedSel).first()\n await btn.scrollIntoViewIfNeeded()\n await btn.click({ force: true })\n await page.waitForTimeout(300)\n } catch { }\n }\n return true\n }\n\n private async extractVideos(page: Page): Promise<VideoResult[]> {\n const vsels = VideoSelectors\n return page.evaluate((sels) => {\n const results: Array<{ type: 'video' | 'short_video'; title: string; channel: string; platform: string; duration: string; url: string }> = []\n const containers = Array.from(document.querySelectorAll(sels.container))\n for (const container of containers) {\n const headingEl = container.querySelector(sels.sectionHeading)\n const headingText = headingEl?.textContent?.trim() ?? ''\n const type: 'video' | 'short_video' = headingText.toLowerCase().includes('short') ? 'short_video' : 'video'\n const items = Array.from(container.querySelectorAll(sels.item))\n for (const a of items) {\n const href = (a as HTMLAnchorElement).href\n if (!href || (!href.includes('youtube') && !href.includes('youtu.be'))) continue\n const raw = a.textContent?.trim() ?? ''\n const ytIdx = raw.indexOf('YouTube')\n if (ytIdx === -1) continue\n const title = raw.slice(0, ytIdx).trim()\n const remainder = raw.slice(ytIdx + 7).replace(/^[·\\s·]+/, '')\n const channelMatch = remainder.match(/^([^·\\n]+)/)\n const channel = channelMatch ? channelMatch[1].trim() : ''\n if (title) results.push({ type, title, channel, platform: 'YouTube', duration: '', url: href })\n }\n }\n return results\n }, vsels)\n }\n\n private async extractForums(page: Page): Promise<ForumResult[]> {\n const fsels = ForumSelectors\n return page.evaluate((sels) => {\n const results: Array<{ title: string; source: string; url: string }> = []\n const sections = Array.from(document.querySelectorAll(sels.section))\n const forumSection = sections.find((s) => s.textContent?.includes('Discussions'))\n if (!forumSection) return results\n const items = Array.from(forumSection.querySelectorAll(sels.item))\n for (const a of items) {\n const href = (a as HTMLAnchorElement).href\n if (!href) continue\n const titleEl = a.querySelector(sels.title)\n const sourceEl = a.querySelector(sels.source)\n const title = titleEl?.textContent?.trim() ?? ''\n const source = sourceEl?.textContent?.trim() ?? ''\n if (title) results.push({ title, source, url: href })\n }\n return results\n }, fsels)\n }\n\n private async extractShortVideos(page: Page, shortUrl: string): Promise<VideoResult[]> {\n try {\n await page.goto(shortUrl, { waitUntil: 'domcontentloaded' })\n await page.waitForTimeout(1500)\n } catch {\n return []\n }\n await this.throwIfCaptcha(page, 'Google short video search')\n\n const svSels = {\n item: ShortVideoSelectors.item,\n platforms: [...ShortVideoSelectors.platforms] as string[],\n }\n\n const raw = await page.evaluate((sels) => {\n const seen = new Set<string>()\n const results: Array<{ title: string; channel: string; platform: string; duration: string; url: string }> = []\n const items = Array.from(document.querySelectorAll(sels.item))\n\n const videoHosts = ['youtube.com', 'youtu.be', 'tiktok.com', 'instagram.com', 'facebook.com', 'fb.watch']\n\n const byHref = new Map<string, string[]>()\n for (const a of items) {\n const href = (a as HTMLAnchorElement).href\n if (!href) continue\n if (!videoHosts.some((h) => href.includes(h))) continue\n const text = a.textContent?.trim() ?? ''\n if (!byHref.has(href)) byHref.set(href, [])\n byHref.get(href)!.push(text)\n }\n\n for (const [href, texts] of byHref.entries()) {\n if (seen.has(href)) continue\n seen.add(href)\n\n const duration = texts.find((t) => /^\\d+:\\d+$/.test(t)) ?? ''\n const titleText = texts.find((t) => !/^\\d+:\\d+$/.test(t) && t.length > 5) ?? ''\n if (!titleText) continue\n\n let title = titleText\n let platform = ''\n let channel = ''\n\n for (const p of sels.platforms) {\n let lastIdx = -1\n let search = 0\n while (true) {\n const found = titleText.indexOf(p, search)\n if (found === -1) break\n lastIdx = found\n search = found + 1\n }\n if (lastIdx === -1) continue\n const after = titleText.slice(lastIdx + p.length)\n const isSourceTag = /^[\\s·]/.test(after) || after.trim() === ''\n if (!isSourceTag) continue\n title = titleText.slice(0, lastIdx).trim()\n platform = p\n const stripped = after.replace(/^[\\s·]+/, '')\n const dotIdx = stripped.indexOf('·')\n channel = (dotIdx === -1 ? stripped : stripped.slice(0, dotIdx)).trim()\n break\n }\n\n if (title) results.push({ title, channel, platform, duration, url: href })\n }\n\n return results\n }, svSels)\n\n return raw.map((r) => ({ type: 'short_video' as const, ...r }))\n }\n\n private async extractWhatPeopleSaying(page: Page): Promise<WhatPeopleSayingCard[]> {\n const sels = WhatPeopleSayingSelectors\n return page.evaluate((s) => {\n const section = Array.from(document.querySelectorAll(s.sectionTag))\n .find((el) => el.textContent?.includes(s.sectionHeadingText))\n ?? document.querySelector('.yG4QQe.TBC9ub.NbhJ1c')\n\n if (!section) return []\n\n return Array.from(section.querySelectorAll(s.card)).map((card) => {\n const link = card.querySelector(s.cardLink) as HTMLAnchorElement | null\n const url = link?.href ?? ''\n\n const titleH1 = card.querySelector(s.titleH1)?.textContent?.trim()\n const titleDiv = card.querySelector(s.titleDiv)?.textContent?.trim()\n const title = titleH1 ?? titleDiv ?? ''\n\n const sourceText = card.querySelector(s.source)?.textContent?.trim() ?? ''\n const platformEl = card.querySelector(s.platformBadge)\n const platformText = platformEl?.textContent?.trim() ?? ''\n\n const ytChannel = card.querySelector(s.ytChannel)?.textContent?.trim() ?? ''\n const ytDate = card.querySelector(s.ytDate)?.textContent?.trim() ?? ''\n const authorNote = card.querySelector(s.authorNote)?.textContent?.trim() ?? null\n\n const commentLabelEl = card.querySelector(s.popularCommentLabel)\n let popularComment: string | null = null\n if (commentLabelEl) {\n let next = commentLabelEl.nextSibling\n while (next) {\n const t = next.textContent?.trim()\n if (t) { popularComment = t; break }\n next = next.nextSibling\n }\n }\n\n const allSpans = Array.from(card.querySelectorAll('span'))\n const duration = allSpans.find((s) => /^\\d+:\\d+$/.test(s.textContent?.trim() ?? ''))?.textContent?.trim() ?? null\n const engagementParts = allSpans\n .map((s) => s.textContent?.trim() ?? '')\n .filter((t) =>\n /\\d/.test(t) && (\n t.includes('comment') || t.includes('reaction') ||\n t.includes('view') || t.includes('like') || t.includes('share')\n )\n )\n const engagement = engagementParts[0] ?? ''\n\n const dateCandidates = allSpans\n .map((s) => s.textContent?.trim() ?? '')\n .filter((t) => /\\d+ (day|week|month|year|hour)s? ago|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/.test(t))\n const date = ytDate || (dateCandidates[0] ?? '')\n\n const platform = platformText || (ytChannel ? 'YouTube' : '')\n const source = ytChannel || sourceText\n\n let type: 'reddit' | 'facebook' | 'youtube' | 'instagram' | 'tiktok' | 'news' | 'unknown' = 'unknown'\n const pl = platform.toLowerCase()\n const src = source.toLowerCase()\n const srcRaw = sourceText.toLowerCase()\n if (pl.includes('reddit') || src.startsWith('r/')) type = 'reddit'\n else if (pl.includes('facebook') || srcRaw.includes('facebook')) type = 'facebook'\n else if (pl.includes('instagram') || srcRaw.includes('instagram')) type = 'instagram'\n else if (pl.includes('tiktok') || srcRaw.includes('tiktok')) type = 'tiktok'\n else if (pl.includes('youtube') || !!ytChannel) type = 'youtube'\n else type = 'news'\n\n return { type, title, url, source, platform, popularComment, engagement, date, duration, authorNote }\n })\n }, sels)\n }\n\n private async extractOrganicResults(page: Page): Promise<OrganicResult[]> {\n const sels = OrganicSelectors\n return page.evaluate((s) => {\n const out: Array<{ position: number; title: string; url: string; domain: string; cite: string | null; snippet: string | null; isRedditStyle: boolean; inlineRating: { value: string; count: string } | null }> = []\n let pos = 0\n document.querySelectorAll(s.result).forEach(card => {\n const titleEl = card.querySelector(s.title)\n if (!titleEl) return\n const title = titleEl.textContent?.trim() ?? ''\n const linkEl = titleEl.closest('a') as HTMLAnchorElement | null\n const url = linkEl?.href ?? ''\n if (!title || !url) return\n pos++\n const cite = card.querySelector(s.cite)?.textContent?.trim() ?? null\n const snippet = card.querySelector(s.snippet)?.textContent?.trim() ?? null\n const isRedditStyle = !!card.querySelector(s.redditCite)\n const ratingEl = card.querySelector(s.ratingWrap)\n const inlineRating = ratingEl\n ? { value: ratingEl.querySelector(s.ratingValue)?.textContent?.trim() ?? '', count: ratingEl.querySelector(s.reviewCount)?.textContent?.trim() ?? '' }\n : null\n let domain = ''\n try { domain = new URL(url).hostname.replace(/^www\\./, '') } catch { domain = card.querySelector(s.siteName)?.textContent?.trim() ?? '' }\n out.push({ position: pos, title, url, domain, cite, snippet, isRedditStyle, inlineRating })\n })\n return out\n }, sels)\n }\n\n private async extractLocalPack(page: Page): Promise<LocalPackBusiness[]> {\n const sels = LocalPackSelectors\n return page.evaluate((s) => {\n const out: Array<{ position: number; name: string; cid: string | null; rating: string | null; reviewCount: string | null; metadata: string[]; websiteUrl: string | null; directionsUrl: string | null }> = []\n let container: Element | null = null\n document.querySelectorAll('[role=\"heading\"]').forEach(h => {\n if (!container && h.textContent?.includes(s.headingText)) container = h.closest('[data-hveid]')\n })\n if (!container) return out\n ;(container as Element).querySelectorAll(s.card).forEach((card, i) => {\n const name = card.querySelector(s.name)?.textContent?.trim() ?? ''\n if (!name) return\n const rating = card.querySelector(s.ratingValue)?.textContent?.trim() ?? null\n const reviewRaw = card.querySelector(s.reviewCount)?.textContent?.trim() ?? null\n const reviewCount = reviewRaw ? reviewRaw.replace(/[()]/g, '').trim() : null\n let cid = (card.querySelector('a[data-cid]') as HTMLAnchorElement | null)?.getAttribute('data-cid') ?? null\n if (!cid) {\n for (const link of Array.from(card.querySelectorAll('a[href]')) as HTMLAnchorElement[]) {\n const m1 = link.href.match(/[?&]cid=(\\d+)/)\n if (m1) { cid = m1[1]; break }\n const m2 = link.href.match(/!1s0x[0-9a-f]+:0x([0-9a-f]+)/i)\n if (m2) { try { cid = BigInt('0x' + m2[1]).toString() } catch { } if (cid) break }\n }\n }\n const metadata: string[] = []\n card.querySelectorAll('div, span').forEach(el => {\n const text = Array.from(el.childNodes)\n .filter(n => n.nodeType === 3)\n .map(n => n.textContent?.trim() ?? '')\n .filter(t => t.length > 1 && t.length < 120)\n .join(' ')\n if (text && !metadata.includes(text)) metadata.push(text)\n })\n const links = Array.from(card.querySelectorAll('a[href]')) as HTMLAnchorElement[]\n const directionsUrl = links.find(a => a.href.includes('google.com/maps'))?.href ?? null\n const websiteUrl = links.find(a => !a.href.includes('google.com') && a.href.startsWith('http'))?.href ?? null\n out.push({ position: i + 1, name, cid, rating, reviewCount, metadata, websiteUrl, directionsUrl })\n })\n return out\n }, sels)\n }\n\n private async extractEntityIds(page: Page): Promise<EntityIds> {\n return page.evaluate(() => {\n const kgIds = new Set<string>()\n const cids = new Set<string>()\n const gcids = new Set<string>()\n const recordMap = new Map<string, { name: string; kgId: string | null; cid: string | null; gcid: string | null }>()\n\n function nameFromWrapper(el: Element): string {\n const sel = ['.OSrXXb', '.dbg0pd', '.tzt0oe', '[role=\"heading\"]', 'h3']\n for (const s of sel) {\n const found = el.querySelector(s)\n if (found?.textContent?.trim()) return found.textContent.trim()\n }\n return ''\n }\n\n document.querySelectorAll('[id^=\"pv-/g/\"]').forEach(wrapper => {\n const raw = wrapper.getAttribute('id')\n if (!raw) return\n const kgId = raw.replace('pv-', '')\n kgIds.add(kgId)\n const name = nameFromWrapper(wrapper)\n const cidEl = wrapper.querySelector('a[data-cid]')\n const cid = cidEl?.getAttribute('data-cid') ?? null\n if (cid) cids.add(cid)\n if (name) recordMap.set(kgId, { name, kgId, cid, gcid: null })\n })\n\n document.querySelectorAll('[data-mid]').forEach(el => {\n const mid = el.getAttribute('data-mid')\n if (!mid?.startsWith('/g/')) return\n kgIds.add(mid)\n if (!recordMap.has(mid)) {\n const name = nameFromWrapper(el)\n if (name) recordMap.set(mid, { name, kgId: mid, cid: null, gcid: null })\n }\n })\n\n document.querySelectorAll('.w7Dbne').forEach(card => {\n const cidEl = card.querySelector('a[data-cid]')\n const cid = cidEl?.getAttribute('data-cid') ?? null\n if (!cid) return\n cids.add(cid)\n const name = card.querySelector('.OSrXXb')?.textContent?.trim() ?? ''\n if (!name) return\n const kgIdEl = card.querySelector('[id^=\"pv-/g/\"]')\n const kgId = kgIdEl ? kgIdEl.getAttribute('id')!.replace('pv-', '') : null\n const key = kgId ?? `cid:${cid}`\n if (recordMap.has(key)) {\n const existing = recordMap.get(key)!\n if (!existing.cid) recordMap.set(key, { ...existing, cid })\n } else {\n recordMap.set(key, { name, kgId, cid, gcid: null })\n }\n })\n\n document.querySelectorAll('a[data-cid]').forEach(el => {\n const cid = el.getAttribute('data-cid')\n if (!cid) return\n cids.add(cid)\n const alreadyNamed = [...recordMap.values()].some(r => r.cid === cid)\n if (!alreadyNamed) {\n let node: Element | null = el.parentElement\n let name = ''\n for (let i = 0; i < 8 && node; i++) {\n const h = node.querySelector('.OSrXXb, .dbg0pd, [role=\"heading\"], h3')\n if (h?.textContent?.trim()) { name = h.textContent.trim(); break }\n node = node.parentElement\n }\n if (name) recordMap.set(`cid:${cid}`, { name, kgId: null, cid, gcid: null })\n }\n })\n\n const scriptContent = Array.from(document.querySelectorAll('script:not([src])'))\n .map(s => s.textContent ?? '')\n .filter(t => t.length > 10_000)\n .join('\\n')\n\n for (const m of scriptContent.matchAll(/\\/g\\/[a-zA-Z0-9_-]{5,20}/g)) kgIds.add(m[0])\n for (const m of scriptContent.matchAll(/gcid:[a-zA-Z0-9_]+/g)) gcids.add(m[0])\n for (const m of scriptContent.matchAll(/0x[0-9a-f]+:0x([0-9a-f]+)/gi)) {\n try { cids.add(BigInt('0x' + m[1]).toString()) } catch { }\n }\n\n return { entities: [...recordMap.values()], kgIds: [...kgIds], cids: [...cids], gcids: [...gcids] }\n })\n }\n\n private mergeLocalPackIntoEntities(entityIds: EntityIds, localPack: LocalPackBusiness[]): EntityIds {\n const cidSet = new Set<string>(entityIds.cids)\n const records = entityIds.entities.map(r => ({ ...r }))\n\n for (const biz of localPack) {\n if (!biz.cid) continue\n cidSet.add(biz.cid)\n const nameNorm = biz.name.toLowerCase().trim()\n const byName = records.find(r => r.name.toLowerCase().trim() === nameNorm)\n if (byName) {\n if (!byName.cid) byName.cid = biz.cid\n } else if (!records.find(r => r.cid === biz.cid)) {\n records.push({ name: biz.name, kgId: null, cid: biz.cid, gcid: null })\n }\n }\n\n return { ...entityIds, entities: records, cids: [...cidSet] }\n }\n\n private async extractAISurfaces(page: Page, options?: HarvestOptions): Promise<{\n surface: GoogleSurface\n aiOverview: AIOverviewResult\n aiMode: AIModeResult\n }> {\n const surfaces = await page.evaluate(extractAISurfacesFromDocument, {\n aio: AIOverviewSelectors,\n aim: AIModeSelectors,\n expandWaitMs: 1500,\n })\n\n if (!surfaces.aiOverview.detected) {\n this.completeness.aioShareCaptured = null\n return surfaces\n }\n\n const shareUrl = this.aioShareUrlEarly\n ?? await this.captureAIOverviewShareUrl(page, options).catch(() => null)\n this.completeness.aioShareCaptured = shareUrl !== null\n return {\n ...surfaces,\n aiOverview: { ...surfaces.aiOverview, shareUrl },\n }\n }\n\n private async captureAIOverviewShareUrl(page: Page, options?: HarvestOptions): Promise<string | null> {\n if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null\n\n const clicked = await page.evaluate(() => {\n const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role=\"heading\"],div'))\n .filter(h => (h.textContent || '').trim() === 'AI Overview')\n for (const head of heads) {\n let sec: HTMLElement | null = head as HTMLElement\n for (let up = 0; up < 8 && sec; up++) {\n const btn = sec.querySelector('[aria-label=\"Share\"]') as HTMLElement | null\n if (btn) { btn.scrollIntoView({ block: 'center' }); btn.click(); return true }\n sec = sec.parentElement\n }\n }\n return false\n }).catch(() => false)\n\n if (!clicked) return null\n\n let url: string | null = null\n try {\n const found = await page.waitForFunction(() => {\n const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)\n while (w.nextNode()) {\n const m = (w.currentNode.textContent || '').match(/https:\\/\\/share\\.google\\/aimode\\/\\S+/)\n if (m) return m[0]\n }\n return false\n }, undefined, { timeout: 4500, polling: 250 })\n url = (await found.jsonValue()) as string\n } catch {\n url = null\n }\n\n await page.keyboard.press('Escape').catch(() => {})\n return url\n }\n\n private buildTree(flat: FlatRow[], _seed: string): PAANode[] {\n const roots: PAANode[] = []\n const nodeMap = new Map<string, PAANode>()\n\n for (const row of flat) {\n const node: PAANode = {\n question: row.question,\n answer: row.answer || null,\n sourceTitle: row.source_title || null,\n sourceSite: row.source_site || null,\n sourceCite: row.source_cite || null,\n depth: row.depth,\n parentQuestion: row.parent_question || null,\n children: [],\n }\n nodeMap.set(row.question, node)\n }\n\n for (const node of nodeMap.values()) {\n if (node.parentQuestion && nodeMap.has(node.parentQuestion)) {\n nodeMap.get(node.parentQuestion)!.children.push(node)\n } else {\n roots.push(node)\n }\n }\n\n return roots\n }\n\n private getBrowserDebugSnapshot(): BrowserDebugSnapshot {\n return this.driver.getDebugSnapshot()\n }\n\n private buildHarvestDebugSnapshot(\n options: HarvestOptions,\n canonicalLocation: string | null,\n uule: string | null,\n locationEvidence?: SerpLocationEvidence,\n ): HarvestDebugSnapshot | undefined {\n if (!options.debug) return undefined\n return {\n enabled: true,\n request: {\n query: options.query,\n locationInput: options.location ?? null,\n canonicalLocation,\n uule,\n gl: options.gl,\n hl: options.hl,\n device: options.device,\n proxyMode: options.proxyMode,\n proxyZip: options.proxyZip ?? null,\n serpOnly: options.serpOnly,\n pages: options.pages ?? 1,\n },\n browser: this.getBrowserDebugSnapshot(),\n ...(locationEvidence ? { locationEvidence } : {}),\n }\n }\n\n async extract(options: HarvestOptions, signal?: AbortSignal): Promise<HarvestResult> {\n const startMs = Date.now()\n this.completeness = { paaWithoutAnswer: 0, paaWithoutSource: 0, paaAnswersRecovered: 0, aioShareCaptured: null }\n this.aioShareUrlEarly = null\n this.collectedRows = []\n this.partialContext = { seed: options.query, location: options.location ?? null, startMs }\n const isMobile = options.device === 'mobile'\n const config: DriverConfig = {\n headless: options.headless,\n profileDir: options.profileDir,\n proxy: options.proxy,\n kernelApiKey: options.kernelApiKey,\n kernelProxyId: options.kernelProxyId,\n kernelProxyResolution: options.kernelProxyResolution as DriverConfig['kernelProxyResolution'],\n proxyMode: options.proxyMode,\n keepDefaultProxy: options.keepDefaultProxy,\n requireUsEgress: options.requireUsEgress,\n viewport: isMobile ? { width: 390, height: 844 } : { width: 1280, height: 800 },\n locale: `${options.hl}-${options.gl.toUpperCase()}`,\n userAgent: isMobile ? MOBILE_USER_AGENT : DESKTOP_USER_AGENT,\n deviceScaleFactor: isMobile ? 3 : 1,\n isMobile,\n hasTouch: isMobile,\n debug: options.debug,\n }\n\n let errorCount = 0\n const diagnosticWarnings: HarvestDiagnosticWarning[] = []\n\n try {\n this.throwIfAborted(signal)\n await this.driver.launch(config)\n this.throwIfAborted(signal)\n const canonicalLocation = options.location ? normalizeLocation(options.location) : null\n const uule = canonicalLocation ? encodeUule(canonicalLocation) : null\n const executedQuery = buildLocalizedSerpQuery(options.query, options.location, canonicalLocation)\n const executionOptions = executedQuery === options.query ? options : { ...options, query: executedQuery }\n const { hasPaa } = await this.driver.navigateToSERP(\n executionOptions.query,\n uule,\n options.gl,\n options.hl,\n {\n ...(options.serpOnly ? { num: 100 } : {}),\n ...(recencyToTbs(options.recency) ? { tbs: recencyToTbs(options.recency) } : {}),\n debug: options.debug,\n },\n )\n this.throwIfAborted(signal)\n\n const page = this.driver.getPage() as Page\n await this.throwIfCaptcha(page, 'Google SERP')\n\n this.aioShareUrlEarly = await this.captureAIOverviewShareUrl(page, options).catch(() => null)\n\n if (options.serpOnly) {\n const [organicResults, localPack, rawEntityIds] = await Promise.all([\n this.extractOrganicResults(page),\n this.extractLocalPack(page),\n this.extractEntityIds(page),\n ])\n const entityIds = this.mergeLocalPackIntoEntities(rawEntityIds, localPack)\n const aiSurfaces = await this.extractAISurfaces(page, options)\n let locationEvidence = options.debug\n ? inferSerpLocationEvidence(canonicalLocation, organicResults, localPack)\n : undefined\n let allOrganic = organicResults\n if ((options.pages ?? 1) >= 2) {\n const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', start: '10' })\n if (recencyToTbs(options.recency)) p2params.set('tbs', recencyToTbs(options.recency) as string)\n if (uule) p2params.set('uule', uule)\n await this.driver.navigateTo('https://www.google.com/search?' + p2params.toString())\n await this.throwIfCaptcha(page, 'Google SERP page 2')\n const p2organic = await this.extractOrganicResults(page)\n allOrganic = [...organicResults, ...p2organic.map(r => ({ ...r, position: r.position + 10 }))]\n if (options.debug) {\n locationEvidence = inferSerpLocationEvidence(canonicalLocation, allOrganic, localPack)\n }\n }\n const stats: HarvestStats = {\n seed: executionOptions.query, totalQuestions: 0, maxDepthReached: 0,\n durationMs: Date.now() - startMs, errorCount,\n }\n this.reporter.onComplete(stats)\n return {\n seed: executionOptions.query, location: options.location ?? null,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'serp_only',\n problem: null,\n completeness: { ...this.completeness },\n ...(options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}),\n },\n totalQuestions: 0,\n surface: aiSurfaces.surface,\n aiOverview: aiSurfaces.aiOverview,\n aiMode: aiSurfaces.aiMode,\n whatPeopleSaying: [], tree: [], flat: [], videos: [], forums: [],\n organicResults: allOrganic, localPack, entityIds, stats,\n }\n }\n\n const [videos, forums, whatPeopleSaying, rawEntityIds, organicResults, localPack] = await Promise.all([\n this.extractVideos(page),\n this.extractForums(page),\n this.extractWhatPeopleSaying(page),\n this.extractEntityIds(page),\n this.extractOrganicResults(page),\n this.extractLocalPack(page),\n ])\n const entityIds = this.mergeLocalPackIntoEntities(rawEntityIds, localPack)\n const initialLocationEvidence = options.debug\n ? inferSerpLocationEvidence(canonicalLocation, organicResults, localPack)\n : undefined\n this.reporter.onVideos(videos)\n this.reporter.onForums(forums)\n\n if (!hasPaa) {\n let noPaaOrganic = organicResults\n let locationEvidence = initialLocationEvidence\n if ((options.pages ?? 1) >= 2) {\n const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', start: '10' })\n if (recencyToTbs(options.recency)) p2params.set('tbs', recencyToTbs(options.recency) as string)\n if (uule) p2params.set('uule', uule)\n await this.driver.navigateTo('https://www.google.com/search?' + p2params.toString())\n await this.throwIfCaptcha(page, 'Google SERP page 2')\n const p2organic = await this.extractOrganicResults(page)\n noPaaOrganic = [...organicResults, ...p2organic.map(r => ({ ...r, position: r.position + 10 }))]\n if (options.debug) {\n locationEvidence = inferSerpLocationEvidence(canonicalLocation, noPaaOrganic, localPack)\n }\n }\n const aiSurfaces = await this.extractAISurfaces(page, options)\n const stats: HarvestStats = {\n seed: executionOptions.query, totalQuestions: 0, maxDepthReached: 0,\n durationMs: Date.now() - startMs, errorCount,\n }\n this.reporter.onComplete(stats)\n return {\n seed: executionOptions.query, location: options.location ?? null,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'no_paa',\n problem: null,\n completeness: { ...this.completeness },\n ...(options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}),\n },\n totalQuestions: 0,\n surface: aiSurfaces.surface,\n aiOverview: aiSurfaces.aiOverview,\n aiMode: aiSurfaces.aiMode,\n whatPeopleSaying, tree: [], flat: [], videos, forums,\n organicResults: noPaaOrganic, localPack, entityIds, stats,\n }\n }\n\n const flat = await this.runBFS(page, executionOptions, signal)\n this.throwIfAborted(signal)\n const aiSurfaces = await this.extractAISurfaces(page, options)\n\n const shortVidsParams = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', udm: ShortVideoSelectors.udm })\n if (uule) shortVidsParams.set('uule', uule)\n let shortVideos: VideoResult[] = []\n try {\n shortVideos = await this.extractShortVideos(page, 'https://www.google.com/search?' + shortVidsParams.toString())\n } catch (err) {\n if (!(err instanceof CaptchaError)) throw err\n errorCount++\n diagnosticWarnings.push({\n code: 'short_videos_captcha_skipped',\n surface: 'short_videos',\n message: err.message,\n retryable: true,\n })\n }\n this.reporter.onVideos(shortVideos)\n\n let allOrganic = organicResults\n let locationEvidence = initialLocationEvidence\n if ((options.pages ?? 1) >= 2) {\n const p2params = new URLSearchParams({ q: executionOptions.query, gl: options.gl, hl: options.hl, pws: '0', start: '10' })\n if (recencyToTbs(options.recency)) p2params.set('tbs', recencyToTbs(options.recency) as string)\n if (uule) p2params.set('uule', uule)\n await this.driver.navigateTo('https://www.google.com/search?' + p2params.toString())\n await this.throwIfCaptcha(page, 'Google SERP page 2')\n const p2organic = await this.extractOrganicResults(page)\n allOrganic = [...organicResults, ...p2organic.map(r => ({ ...r, position: r.position + 10 }))]\n if (options.debug) {\n locationEvidence = inferSerpLocationEvidence(canonicalLocation, allOrganic, localPack)\n }\n }\n\n const allVideos = [...videos, ...shortVideos]\n const tree = this.buildTree(flat, executionOptions.query)\n\n const stats: HarvestStats = {\n seed: executionOptions.query,\n totalQuestions: flat.length,\n maxDepthReached: flat.reduce((m, r) => Math.max(m, r.depth), 0),\n durationMs: Date.now() - startMs,\n errorCount,\n }\n\n this.reporter.onComplete(stats)\n\n return {\n seed: executionOptions.query,\n location: options.location ?? null,\n extractedAt: new Date().toISOString(),\n diagnostics: {\n completionStatus: 'paa_found',\n problem: null,\n completeness: { ...this.completeness },\n ...(diagnosticWarnings.length > 0 ? { warnings: diagnosticWarnings } : {}),\n ...(options.debug ? { debug: this.buildHarvestDebugSnapshot(executionOptions, canonicalLocation, uule, locationEvidence) } : {}),\n },\n totalQuestions: flat.length,\n surface: aiSurfaces.surface,\n aiOverview: aiSurfaces.aiOverview,\n aiMode: aiSurfaces.aiMode,\n whatPeopleSaying,\n tree,\n flat,\n videos: allVideos,\n forums,\n organicResults: allOrganic,\n localPack,\n entityIds,\n stats,\n }\n } catch (err) {\n errorCount++\n this.reporter.onError(err instanceof Error ? err : new Error(String(err)))\n throw err\n }\n }\n}\n","import { promises as fs } from 'node:fs'\nimport path from 'node:path'\nimport Papa from 'papaparse'\nimport type { HarvestResult, FlatRow, VideoResult, ForumResult, AICitation, WhatPeopleSayingCard } from '../types.js'\nimport type { IOutputSerializer } from './IOutputSerializer.js'\nimport { postToMemoryLibrary } from './memory-library-sink.js'\n\nexport class OutputSerializer implements IOutputSerializer {\n async writeJSON(result: HarvestResult, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = result.seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const filename = `${slug}-${Date.now()}.json`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, JSON.stringify(result, null, 2), 'utf8')\n await postToMemoryLibrary({ title: `${result.seed} scrape`, content: JSON.stringify(result), source: `mcp-scraper:${fullPath}` })\n return fullPath\n }\n\n async writeCSV(rows: FlatRow[], outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const seedRaw = rows[0]?.seed_query ?? 'paa'\n const slug = seedRaw.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeVideoCSV(videos: VideoResult[], seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const csv = Papa.unparse(videos, { header: true })\n const filename = `${slug}-videos-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeForumCSV(forums: ForumResult[], seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const csv = Papa.unparse(forums, { header: true })\n const filename = `${slug}-forums-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeAIOverviewCSV(citations: AICitation[], text: string | null, seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const rows = citations.map((c, i) => ({\n seed_query: seed,\n response_text: i === 0 ? (text ?? '') : '',\n citation_text: c.text,\n citation_href: c.href,\n }))\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-ai-overview-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeAIModeCSV(citations: AICitation[], text: string | null, seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const rows = citations.map((c, i) => ({\n seed_query: seed,\n response_text: i === 0 ? (text ?? '') : '',\n citation_text: c.text,\n citation_href: c.href,\n }))\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-ai-mode-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n\n async writeWhatPeopleSayingCSV(cards: WhatPeopleSayingCard[], seed: string, outputDir: string): Promise<string> {\n await fs.mkdir(outputDir, { recursive: true })\n const slug = seed.toLowerCase().replace(/\\W+/g, '-').slice(0, 40)\n const rows = cards.map((c) => ({ seed_query: seed, ...c }))\n const csv = Papa.unparse(rows, { header: true })\n const filename = `${slug}-what-people-saying-${Date.now()}.csv`\n const fullPath = path.join(outputDir, filename)\n await fs.writeFile(fullPath, csv, 'utf8')\n return fullPath\n }\n}\n","export async function postToMemoryLibrary(args: { title: string; content: string; source: string }): Promise<void> {\n const url = process.env.MCP_MEMORY_INGEST_URL\n const key = process.env.MCP_MEMORY_KEY\n if (!url || !key) return\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ data: { apiKey: key, title: args.title, content: args.content, source: args.source } }),\n })\n const body = (await res.json().catch(() => null)) as { result?: { error?: boolean; message?: string } } | null\n if (!res.ok || body?.result?.error) {\n console.warn('[memory-library-sink] ingest not accepted:', res.status, body?.result?.message ?? '')\n }\n } catch (err) {\n console.warn('[memory-library-sink] ingest failed:', (err as Error)?.message)\n }\n}\n\nexport function memoryLibrarySinkEnabled(): boolean {\n return Boolean(process.env.MCP_MEMORY_INGEST_URL && process.env.MCP_MEMORY_KEY)\n}\n","import type { PAANode, HarvestStats, VideoResult, ForumResult } from '../types.js'\nimport type { IProgressReporter } from './IProgressReporter.js'\n\nexport class ProgressReporter implements IProgressReporter {\n onQuestion(node: PAANode): void {\n process.stdout.write(JSON.stringify({ event: 'question', depth: node.depth, question: node.question }) + '\\n')\n }\n\n onDepth(depth: number): void {\n process.stdout.write(JSON.stringify({ event: 'depth', depth }) + '\\n')\n }\n\n onVideos(videos: VideoResult[]): void {\n for (const v of videos) {\n process.stdout.write(JSON.stringify({ event: 'video', type: v.type, platform: v.platform, duration: v.duration, title: v.title, channel: v.channel, url: v.url }) + '\\n')\n }\n }\n\n onForums(forums: ForumResult[]): void {\n for (const f of forums) {\n process.stdout.write(JSON.stringify({ event: 'forum', title: f.title, source: f.source, url: f.url }) + '\\n')\n }\n }\n\n onComplete(stats: HarvestStats): void {\n process.stdout.write(JSON.stringify({ event: 'complete', ...stats }) + '\\n')\n }\n\n onError(err: Error): void {\n process.stderr.write(JSON.stringify({ event: 'error', type: err.constructor.name, message: err.message }) + '\\n')\n }\n}\n","import Kernel from '@onkernel/sdk'\nimport { backupProxyAvailable, createBackupProxyId, scrubBackupProxyText } from './backup-proxy.js'\nimport { normalizeLocation } from './uule.js'\nimport type { CanonicalLocationName, KernelLocationProxyTarget, KernelProxyResolutionDebug, ProxyMode } from './types.js'\n\ntype KernelProxyConfig = {\n country?: string\n state?: string\n city?: string\n zip?: string\n}\n\ntype KernelProxyListItem = {\n id?: string\n name?: string\n type?: string\n status?: string\n config?: KernelProxyConfig\n}\n\ntype KernelProxyCreateResponse = {\n id?: string\n name?: string\n type?: string\n status?: string\n config?: KernelProxyConfig\n}\n\nexport interface ResolveKernelProxyOptions {\n kernelApiKey?: string\n proxyMode: ProxyMode\n configuredKernelProxyId?: string\n location?: string\n proxyZip?: string\n gl: string\n attemptIndex?: number\n fresh?: boolean\n backupOnExhaustion?: boolean\n}\n\nexport interface ResolveKernelProxyResult {\n kernelProxyId?: string\n resolution: KernelProxyResolutionDebug\n disposableProxyId?: string\n}\n\nconst US_STATE_CODES: Record<string, string> = {\n alabama: 'AL',\n alaska: 'AK',\n arizona: 'AZ',\n arkansas: 'AR',\n california: 'CA',\n colorado: 'CO',\n connecticut: 'CT',\n delaware: 'DE',\n florida: 'FL',\n georgia: 'GA',\n hawaii: 'HI',\n idaho: 'ID',\n illinois: 'IL',\n indiana: 'IN',\n iowa: 'IA',\n kansas: 'KS',\n kentucky: 'KY',\n louisiana: 'LA',\n maine: 'ME',\n maryland: 'MD',\n massachusetts: 'MA',\n michigan: 'MI',\n minnesota: 'MN',\n mississippi: 'MS',\n missouri: 'MO',\n montana: 'MT',\n nebraska: 'NE',\n nevada: 'NV',\n 'new hampshire': 'NH',\n 'new jersey': 'NJ',\n 'new mexico': 'NM',\n 'new york': 'NY',\n 'north carolina': 'NC',\n 'north dakota': 'ND',\n ohio: 'OH',\n oklahoma: 'OK',\n oregon: 'OR',\n pennsylvania: 'PA',\n 'rhode island': 'RI',\n 'south carolina': 'SC',\n 'south dakota': 'SD',\n tennessee: 'TN',\n texas: 'TX',\n utah: 'UT',\n vermont: 'VT',\n virginia: 'VA',\n washington: 'WA',\n 'west virginia': 'WV',\n wisconsin: 'WI',\n wyoming: 'WY',\n}\n\nconst US_CITY_CENTER_ZIPS: Record<string, string> = {\n 'atlanta|GA': '30303',\n 'austin|TX': '78701',\n 'baltimore|MD': '21201',\n 'boston|MA': '02108',\n 'boulder|CO': '80302',\n 'charlotte|NC': '28202',\n 'chicago|IL': '60601',\n 'colorado_springs|CO': '80903',\n 'columbus|OH': '43215',\n 'dallas|TX': '75201',\n 'denver|CO': '80202',\n 'detroit|MI': '48226',\n 'fort_collins|CO': '80524',\n 'fort_worth|TX': '76102',\n 'houston|TX': '77002',\n 'indianapolis|IN': '46204',\n 'jacksonville|FL': '32202',\n 'las_vegas|NV': '89101',\n 'los_angeles|CA': '90012',\n 'louisville|KY': '40202',\n 'loveland|CO': '80537',\n 'memphis|TN': '38103',\n 'miami|FL': '33131',\n 'minneapolis|MN': '55401',\n 'nashville|TN': '37203',\n 'new_york|NY': '10001',\n 'orlando|FL': '32801',\n 'philadelphia|PA': '19103',\n 'phoenix|AZ': '85004',\n 'portland|OR': '97205',\n 'raleigh|NC': '27601',\n 'richmond|VA': '23219',\n 'sacramento|CA': '95814',\n 'salt_lake_city|UT': '84101',\n 'san_antonio|TX': '78205',\n 'san_diego|CA': '92101',\n 'san_francisco|CA': '94103',\n 'san_jose|CA': '95113',\n 'seattle|WA': '98101',\n}\n\nfunction proxyIdSuffix(proxyId: string | null | undefined): string | null {\n return proxyId ? proxyId.slice(-6) : null\n}\n\nfunction resolution(\n source: KernelProxyResolutionDebug['source'],\n proxyMode: ProxyMode,\n proxyId: string | undefined,\n target: KernelLocationProxyTarget | null,\n error: string | null,\n disposable = false,\n): ResolveKernelProxyResult {\n const proxyType = source === 'disabled' ? 'none'\n : source === 'configured_fallback' ? 'configured'\n : source === 'isp_created' ? 'isp'\n : source === 'backup_custom' ? 'custom'\n : source === 'location_created' || source === 'location_reused' ? 'residential'\n : 'unknown'\n return {\n kernelProxyId: proxyId,\n ...(disposable && proxyId ? { disposableProxyId: proxyId } : {}),\n resolution: {\n source,\n proxyType,\n proxyMode,\n proxyIdPresent: Boolean(proxyId),\n proxyIdSuffix: proxyIdSuffix(proxyId),\n target,\n error,\n disposable,\n },\n }\n}\n\nfunction normalizeStateName(value: string): string {\n return value.trim().toLowerCase().replace(/\\s+/g, ' ')\n}\n\nfunction normalizeCountryName(value: string): string {\n return value.trim().toLowerCase().replace(/\\./g, '').replace(/\\s+/g, ' ')\n}\n\nfunction isUnitedStates(country: string | undefined): boolean {\n if (!country) return true\n const normalized = normalizeCountryName(country)\n return normalized === 'united states' || normalized === 'united states of america' || normalized === 'usa' || normalized === 'us'\n}\n\nfunction stateCodeFor(region: string): string | null {\n const trimmed = region.trim()\n if (/^[A-Za-z]{2}$/.test(trimmed)) return trimmed.toUpperCase()\n return US_STATE_CODES[normalizeStateName(trimmed)] ?? null\n}\n\nexport function kernelCityIdentifierCandidates(city: string): string[] {\n const ascii = city\n .normalize('NFKD')\n .replace(/[^\\x00-\\x7F]/g, '')\n .toLowerCase()\n const words = ascii.split(/[^a-z0-9]+/).filter(Boolean)\n const underscored = words.join('_')\n const compact = words.join('')\n return Array.from(new Set([underscored, compact].filter(Boolean)))\n}\n\nfunction proxyName(country: string, state: string, city?: string): string {\n return city\n ? `mcp-serp-residential-${country.toLowerCase()}-${state.toLowerCase()}-${city}`\n : `mcp-serp-residential-${country.toLowerCase()}-${state.toLowerCase()}`\n}\n\nfunction freshProxyName(baseName: string, attemptIndex: number | undefined): string {\n const stamp = `${Date.now()}-${attemptIndex ?? 0}-${Math.random().toString(36).slice(2, 8)}`\n return `${baseName}-fresh-${stamp}`\n}\n\nfunction zipProxyName(zip: string): string {\n return `mcp-serp-residential-us-zip-${zip}`\n}\n\nexport function parseKernelLocationProxyTarget(location: string | undefined, gl: string): KernelLocationProxyTarget | null {\n if (!location || gl.toLowerCase() !== 'us') return null\n const canonicalLocation = normalizeLocation(location)\n let parts = canonicalLocation.split(',').map(part => part.trim()).filter(Boolean)\n if (parts.length > 1 && isUnitedStates(parts[parts.length - 1])) {\n parts = parts.slice(0, -1)\n }\n\n if (parts.length === 1) {\n const stateOnly = stateCodeFor(parts[0])\n if (!stateOnly) return null\n return {\n canonicalLocation: canonicalLocation as CanonicalLocationName,\n level: 'state',\n country: 'US',\n state: stateOnly,\n city: '',\n cityCandidates: [],\n proxyName: proxyName('US', stateOnly),\n config: {\n country: 'US',\n state: stateOnly,\n },\n }\n }\n\n const [city = '', region = ''] = parts\n if (!city || !region) return null\n const state = stateCodeFor(region)\n if (!state) return null\n const cityCandidates = kernelCityIdentifierCandidates(city)\n const primaryCity = cityCandidates[0]\n if (!primaryCity) return null\n return {\n canonicalLocation: canonicalLocation as CanonicalLocationName,\n level: 'city',\n country: 'US',\n state,\n city: primaryCity,\n cityCandidates,\n proxyName: proxyName('US', state, primaryCity),\n config: {\n country: 'US',\n state,\n city: primaryCity,\n },\n }\n}\n\nfunction cityZipKey(target: KernelLocationProxyTarget): string {\n return `${target.city}|${target.state}`\n}\n\nfunction knownZipFor(target: KernelLocationProxyTarget, explicitZip: string | undefined): string | null {\n if (explicitZip && /^\\d{5}$/.test(explicitZip)) return explicitZip\n return US_CITY_CENTER_ZIPS[cityZipKey(target)] ?? null\n}\n\nfunction zipTarget(target: KernelLocationProxyTarget, zip: string): KernelLocationProxyTarget {\n return {\n ...target,\n level: 'zip',\n zip,\n proxyName: zipProxyName(zip),\n config: {\n country: target.country,\n state: target.state,\n zip,\n },\n }\n}\n\nfunction withProxyName(target: KernelLocationProxyTarget, name: string): KernelLocationProxyTarget {\n return {\n ...target,\n proxyName: name,\n }\n}\n\nfunction configMatches(config: KernelProxyConfig | undefined, target: KernelLocationProxyTarget, city?: string): boolean {\n if (target.level === 'zip') {\n return config?.country?.toUpperCase() === target.country && config?.zip === target.zip\n }\n return config?.country?.toUpperCase() === target.country &&\n config?.state?.toUpperCase() === target.state &&\n (city ? config?.city === city : !config?.city)\n}\n\nfunction findExistingTargetProxy(proxies: KernelProxyListItem[], target: KernelLocationProxyTarget): KernelProxyListItem | null {\n return proxies.find(proxy => (\n proxy.type === 'residential' &&\n proxy.status !== 'unavailable' &&\n Boolean(proxy.id) &&\n (proxy.name === target.proxyName || configMatches(proxy.config, target, target.level === 'city' ? target.city : undefined))\n )) ?? null\n}\n\nfunction findExistingProxy(proxies: KernelProxyListItem[], target: KernelLocationProxyTarget): KernelProxyListItem | null {\n for (const city of target.cityCandidates) {\n const name = proxyName(target.country, target.state, city)\n const found = proxies.find(proxy => (\n proxy.type === 'residential' &&\n proxy.status !== 'unavailable' &&\n Boolean(proxy.id) &&\n (proxy.name === name || configMatches(proxy.config, target, city))\n ))\n if (found) return found\n }\n return null\n}\n\nfunction stateTarget(target: KernelLocationProxyTarget): KernelLocationProxyTarget {\n return {\n ...target,\n level: 'state',\n proxyName: proxyName(target.country, target.state),\n config: {\n country: target.country,\n state: target.state,\n },\n }\n}\n\nfunction findExistingStateProxy(proxies: KernelProxyListItem[], target: KernelLocationProxyTarget): KernelProxyListItem | null {\n const name = proxyName(target.country, target.state)\n return proxies.find(proxy => (\n proxy.type === 'residential' &&\n proxy.status !== 'unavailable' &&\n Boolean(proxy.id) &&\n (proxy.name === name || configMatches(proxy.config, target))\n )) ?? null\n}\n\nfunction escalatedTargetLevel(target: KernelLocationProxyTarget, attemptIndex: number): KernelLocationProxyTarget {\n return stateTarget(target)\n}\n\nfunction errorText(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction freshTargetCandidates(\n target: KernelLocationProxyTarget,\n explicitZip: string | undefined,\n attemptIndex: number | undefined,\n): KernelLocationProxyTarget[] {\n const out: KernelLocationProxyTarget[] = []\n const zip = knownZipFor(target, explicitZip)\n if (zip) {\n const targetZip = zipTarget(target, zip)\n out.push(withProxyName(targetZip, freshProxyName(targetZip.proxyName, attemptIndex)))\n }\n\n for (const city of target.cityCandidates) {\n const cityTarget: KernelLocationProxyTarget = {\n ...target,\n level: 'city',\n city,\n proxyName: proxyName(target.country, target.state, city),\n config: {\n country: target.country,\n state: target.state,\n city,\n },\n }\n out.push(withProxyName(cityTarget, freshProxyName(cityTarget.proxyName, attemptIndex)))\n }\n\n const fallbackTarget = stateTarget(target)\n out.push(withProxyName(fallbackTarget, freshProxyName(fallbackTarget.proxyName, attemptIndex)))\n return out\n}\n\nasync function settleCreateExhaustion(\n options: ResolveKernelProxyOptions,\n target: KernelLocationProxyTarget | null,\n createErrors: string[],\n): Promise<ResolveKernelProxyResult> {\n if (options.backupOnExhaustion && createErrors.length > 0 && options.kernelApiKey && backupProxyAvailable()) {\n try {\n const backupId = await createBackupProxyId(options.kernelApiKey, { attemptIndex: options.attemptIndex })\n return resolution('backup_custom', options.proxyMode, backupId, target, null, true)\n } catch (err) {\n createErrors.push(`backup: ${scrubBackupProxyText(errorText(err))}`)\n }\n }\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(' | '))\n}\n\nexport async function resolveBackupKernelProxyId(\n options: Pick<ResolveKernelProxyOptions, 'kernelApiKey' | 'proxyMode' | 'attemptIndex'>,\n): Promise<ResolveKernelProxyResult | null> {\n if (!options.kernelApiKey || !backupProxyAvailable()) return null\n try {\n const backupId = await createBackupProxyId(options.kernelApiKey, { attemptIndex: options.attemptIndex })\n return resolution('backup_custom', options.proxyMode, backupId, null, null, true)\n } catch {\n return null\n }\n}\n\nasync function createFreshLocationProxy(\n kernel: Kernel,\n options: ResolveKernelProxyOptions,\n target: KernelLocationProxyTarget,\n): Promise<ResolveKernelProxyResult> {\n const createErrors: string[] = []\n\n for (const candidate of freshTargetCandidates(target, options.proxyZip, options.attemptIndex)) {\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: candidate.proxyName,\n config: candidate.level === 'zip'\n ? { country: candidate.country, zip: candidate.zip }\n : candidate.config,\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, candidate, null, true)\n }\n createErrors.push(`${candidate.proxyName}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${candidate.proxyName}: ${errorText(err)}`)\n }\n }\n\n if (process.env.KERNEL_PROXY_TYPE !== 'residential') {\n const ispTarget = stateTarget(target)\n try {\n const created = await kernel.proxies.create({\n type: 'isp',\n name: freshProxyName(`mcp-serp-isp-${ispTarget.country.toLowerCase()}-${(ispTarget.state ?? '').toLowerCase()}`, options.attemptIndex),\n config: { country: ispTarget.country, ...(ispTarget.state ? { state: ispTarget.state } : {}) },\n } as never) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('isp_created', options.proxyMode, created.id, ispTarget, null, true)\n }\n createErrors.push('isp: Kernel did not return a proxy id')\n } catch (err) {\n createErrors.push(`isp: ${errorText(err)}`)\n }\n }\n\n return settleCreateExhaustion(options, target, createErrors)\n}\n\nexport async function deleteKernelProxyId(kernelApiKey: string | undefined, proxyId: string | undefined): Promise<void> {\n if (!kernelApiKey || !proxyId) return\n const kernel = new Kernel({ apiKey: kernelApiKey })\n await kernel.proxies.delete(proxyId)\n}\n\nexport async function createFreshUsIspProxy(kernelApiKey: string, attemptIndex = 0): Promise<string> {\n const kernel = new Kernel({ apiKey: kernelApiKey })\n const created = await kernel.proxies.create({\n type: 'isp',\n name: freshProxyName('mcp-serp-isp-us', attemptIndex),\n config: { country: 'US' },\n } as never) as KernelProxyCreateResponse\n if (!created.id) throw new Error('Kernel did not return a US ISP proxy id')\n return created.id\n}\n\nexport async function resolveKernelProxyId(options: ResolveKernelProxyOptions): Promise<ResolveKernelProxyResult> {\n if (options.proxyMode === 'none') {\n return resolution('disabled', options.proxyMode, undefined, null, null)\n }\n\n if (options.proxyMode === 'configured') {\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, null, null)\n }\n\n const target = parseKernelLocationProxyTarget(options.location, options.gl)\n if (!target || !options.kernelApiKey) {\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, target, target ? null : 'location could not be normalized to a US city/state proxy target')\n }\n\n const kernel = new Kernel({ apiKey: options.kernelApiKey })\n try {\n const attemptIndex = options.attemptIndex ?? 0\n\n if (options.fresh) {\n return await createFreshLocationProxy(kernel, options, target)\n }\n\n if (attemptIndex >= 1) {\n const escalatedTarget = escalatedTargetLevel(target, attemptIndex)\n const createErrors: string[] = []\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: escalatedTarget.proxyName,\n config: escalatedTarget.config,\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, escalatedTarget, null)\n }\n createErrors.push(`${escalatedTarget.state}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${escalatedTarget.state}: ${errorText(err)}`)\n }\n return settleCreateExhaustion(options, escalatedTarget, createErrors)\n }\n\n const proxies = await kernel.proxies.list() as KernelProxyListItem[]\n const zip = knownZipFor(target, options.proxyZip)\n const createErrors: string[] = []\n if (zip) {\n const targetZip = zipTarget(target, zip)\n const existingZip = findExistingTargetProxy(proxies, targetZip)\n if (existingZip?.id) {\n return resolution('location_reused', options.proxyMode, existingZip.id, targetZip, null)\n }\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: targetZip.proxyName,\n config: {\n country: targetZip.country,\n zip,\n },\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, targetZip, null)\n }\n createErrors.push(`${zip}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${zip}: ${errorText(err)}`)\n }\n }\n\n const existing = findExistingProxy(proxies, target)\n if (existing?.id) {\n return resolution('location_reused', options.proxyMode, existing.id, target, createErrors.join(' | ') || null)\n }\n\n for (const city of target.cityCandidates) {\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: proxyName(target.country, target.state, city),\n config: {\n country: target.country,\n state: target.state,\n city,\n },\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, {\n ...target,\n level: 'city',\n city,\n proxyName: proxyName(target.country, target.state, city),\n config: {\n country: target.country,\n state: target.state,\n city,\n },\n }, null)\n }\n createErrors.push(`${city}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${city}: ${errorText(err)}`)\n }\n }\n const fallbackTarget = stateTarget(target)\n const existingState = findExistingStateProxy(proxies, fallbackTarget)\n if (existingState?.id) {\n return resolution('location_reused', options.proxyMode, existingState.id, fallbackTarget, createErrors.join(' | '))\n }\n try {\n const created = await kernel.proxies.create({\n type: 'residential',\n name: fallbackTarget.proxyName,\n config: fallbackTarget.config,\n }) as KernelProxyCreateResponse\n if (created.id) {\n return resolution('location_created', options.proxyMode, created.id, fallbackTarget, createErrors.join(' | '))\n }\n createErrors.push(`${fallbackTarget.state}: Kernel did not return a proxy id`)\n } catch (err) {\n createErrors.push(`${fallbackTarget.state}: ${errorText(err)}`)\n }\n return settleCreateExhaustion(options, target, createErrors)\n } catch (err) {\n return resolution('configured_fallback', options.proxyMode, options.configuredKernelProxyId, target, errorText(err))\n }\n}\n","import { randomInt } from 'node:crypto'\nimport Kernel from '@onkernel/sdk'\n\nconst BACKUP_PROXY_HOST = 'pr.oxylabs.io'\nconst BACKUP_PROXY_PORT = 7777\nconst BACKUP_STICKY_SESSION_MINUTES = 10\n\nexport const BACKUP_LAST_RESORT_ATTEMPTS = 2\n\ntype BackupProxyCreateResponse = {\n id?: string\n}\n\nexport function backupProxyUsername(): string | undefined {\n const value = process.env.OXYLABS_USERNAME?.trim()\n return value || undefined\n}\n\nexport function backupProxyPassword(): string | undefined {\n const value = process.env.OXYLABS_PASSWORD?.trim()\n return value || undefined\n}\n\nexport function backupProxyCountry(): string {\n const value = process.env.OXYLABS_COUNTRY?.trim()\n return (value || 'US').toUpperCase()\n}\n\nexport function backupProxyAvailable(): boolean {\n return Boolean(backupProxyUsername() && backupProxyPassword())\n}\n\nfunction randomStickySessionId(): string {\n let digits = ''\n for (let i = 0; i < 10; i++) digits += String(randomInt(0, 10))\n return digits\n}\n\nexport function buildBackupUsername(opts: { country?: string; sticky?: boolean } = {}): string | undefined {\n const user = backupProxyUsername()\n if (!user) return undefined\n const base = user.startsWith('customer-') ? user : `customer-${user}`\n const country = (opts.country?.trim() || backupProxyCountry()).toUpperCase()\n const sticky = opts.sticky ? `-sessid-${randomStickySessionId()}-sesstime-${BACKUP_STICKY_SESSION_MINUTES}` : ''\n return `${base}-cc-${country}${sticky}`\n}\n\nexport function scrubBackupProxyText(message: string): string {\n return message\n .replace(/pr\\.oxylabs\\.io(:\\d+)?/gi, 'backup proxy')\n .replace(/oxylabs/gi, 'backup proxy')\n}\n\nfunction backupProxyName(attemptIndex: number | undefined): string {\n const stamp = `${Date.now()}-${attemptIndex ?? 0}-${Math.random().toString(36).slice(2, 8)}`\n return `mcp-backup-custom-${stamp}`\n}\n\nexport async function createBackupProxyId(\n kernelApiKey: string,\n opts: { sticky?: boolean; attemptIndex?: number } = {},\n): Promise<string> {\n const username = buildBackupUsername({ sticky: opts.sticky })\n const password = backupProxyPassword()\n if (!username || !password) throw new Error('backup proxy pool is not configured')\n const kernel = new Kernel({ apiKey: kernelApiKey })\n try {\n const created = await kernel.proxies.create({\n type: 'custom',\n name: backupProxyName(opts.attemptIndex),\n protocol: 'http',\n config: {\n host: BACKUP_PROXY_HOST,\n port: BACKUP_PROXY_PORT,\n username,\n password,\n },\n }) as BackupProxyCreateResponse\n if (!created.id) throw new Error('backup proxy creation did not return a proxy id')\n return created.id\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(scrubBackupProxyText(message))\n }\n}\n\nexport async function createBackupProxyIdSafe(\n kernelApiKey: string | undefined,\n attemptIndex: number,\n): Promise<string | undefined> {\n if (!kernelApiKey || !backupProxyAvailable()) return undefined\n try {\n return await createBackupProxyId(kernelApiKey, { attemptIndex })\n } catch {\n return undefined\n }\n}\n\nexport async function cleanupBackupProxyId(\n kernelApiKey: string | undefined,\n proxyId: string | undefined,\n): Promise<void> {\n if (!kernelApiKey || !proxyId) return\n try {\n const kernel = new Kernel({ apiKey: kernelApiKey })\n await kernel.proxies.delete(proxyId)\n } catch {\n void 0\n }\n}\n\nexport async function withBackupProxyCleanup<T>(\n kernelApiKey: string | undefined,\n proxyId: string | undefined,\n fn: (proxyId: string | undefined) => Promise<T>,\n): Promise<T> {\n try {\n return await fn(proxyId)\n } finally {\n await cleanupBackupProxyId(kernelApiKey, proxyId)\n }\n}\n","import { DEFAULT_PROXY_MODE, HarvestOptionsSchema } from './schemas.js'\nimport { browserServiceApiKey, browserServiceProxyId } from './lib/browser-service-env.js'\nimport type { HarvestDebugSnapshot, HarvestOptions, HarvestResult, ProxyMode } from './types.js'\nimport { BrowserDriver } from './driver/BrowserDriver.js'\nimport { PAAExtractor } from './extractor/PAAExtractor.js'\nimport { OutputSerializer } from './output/OutputSerializer.js'\nimport { ProgressReporter } from './output/ProgressReporter.js'\nimport { CaptchaError, LocationMismatchError, RequestAbortedError, sanitizeVendorName } from './errors.js'\nimport type { BrowserCloseResult } from './driver/IBrowserDriver.js'\nimport { deleteKernelProxyId, resolveBackupKernelProxyId, resolveKernelProxyId } from './kernel-proxy-resolver.js'\nimport { BACKUP_LAST_RESORT_ATTEMPTS, backupProxyAvailable } from './backup-proxy.js'\nimport { currentCostContext, runWithCostContext } from './api/cost-context.js'\n\nconst MAX_ATTEMPTS = 3\nconst LOCATION_PROXY_MAX_ATTEMPTS = 5\n\nexport type HarvestAttemptOutcome =\n | 'paa_found'\n | 'paa_partial'\n | 'no_paa'\n | 'serp_only'\n | 'degraded_result'\n | 'captcha'\n | 'location_mismatch'\n | 'proxy_tunnel_failed'\n | 'proxy_unavailable'\n | 'request_aborted'\n | 'timeout'\n | 'error'\n\nexport type HarvestAttemptLogEvent =\n | {\n type: 'started'\n attemptNumber: number\n maxAttempts: number\n query: string\n location: string | null\n maxQuestions: number\n startedAt: string\n }\n | {\n type: 'finished'\n attemptNumber: number\n maxAttempts: number\n outcome: HarvestAttemptOutcome\n kernelSessionId: string | null\n questionCount: number\n durationMs: number\n error: string | null\n willRetry: boolean\n cleanup: BrowserCloseResult\n debug: HarvestDebugSnapshot | null\n completedAt: string\n }\n\ntype HarvestAttemptLogSink = (event: HarvestAttemptLogEvent) => void | Promise<void>\n\ntype ExtractOnceOutcome =\n | { result: HarvestResult; error: null; cleanup: BrowserCloseResult; debug: HarvestDebugSnapshot | null; salvagedFrom?: unknown }\n | { result: null; error: unknown; cleanup: BrowserCloseResult; debug: HarvestDebugSnapshot | null; salvagedFrom?: undefined }\n\nfunction abortReason(signal: AbortSignal): unknown {\n if (signal.reason instanceof DOMException && signal.reason.name === 'TimeoutError') return signal.reason\n return new RequestAbortedError()\n}\n\nfunction getAbortSignal(rawOptions: unknown): AbortSignal | undefined {\n if (!rawOptions || typeof rawOptions !== 'object') return undefined\n const signal = (rawOptions as { signal?: unknown }).signal\n if (signal instanceof AbortSignal) return signal\n return undefined\n}\n\nfunction getAttemptLogSink(rawOptions: unknown): HarvestAttemptLogSink | undefined {\n if (!rawOptions || typeof rawOptions !== 'object') return undefined\n const sink = (rawOptions as { onAttemptEvent?: unknown }).onAttemptEvent\n return typeof sink === 'function' ? sink as HarvestAttemptLogSink : undefined\n}\n\nasync function emitAttemptEvent(sink: HarvestAttemptLogSink | undefined, event: HarvestAttemptLogEvent): Promise<void> {\n if (!sink) return\n try {\n await sink(event)\n } catch (err) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_log_failed',\n attempt_number: event.attemptNumber,\n message: err instanceof Error ? err.message : String(err),\n }))\n }\n}\n\nfunction classifyAttemptError(err: unknown): HarvestAttemptOutcome {\n if (err instanceof CaptchaError) return 'captcha'\n if (err instanceof LocationMismatchError) return 'location_mismatch'\n if (err instanceof RequestAbortedError) return 'request_aborted'\n if (err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError')) return 'timeout'\n const message = err instanceof Error ? err.message : String(err)\n if (looksLikeProxyTunnelFailure(message)) return 'proxy_tunnel_failed'\n if (looksLikeProxyUnavailable(message)) return 'proxy_unavailable'\n return /timeout|timed out|Timeout \\d+ms exceeded|deadline/i.test(message) ? 'timeout' : 'error'\n}\n\nfunction classifyAttemptResult(result: HarvestResult): HarvestAttemptOutcome {\n if (result.diagnostics.degradedResult) return 'degraded_result'\n return result.diagnostics?.completionStatus ?? (result.totalQuestions > 0 ? 'paa_found' : 'no_paa')\n}\n\nfunction resultHasUsableContent(result: HarvestResult): boolean {\n return result.flat.length > 0 ||\n result.organicResults.length > 0 ||\n result.localPack.length > 0 ||\n result.videos.length > 0 ||\n result.forums.length > 0 ||\n result.whatPeopleSaying.length > 0 ||\n result.aiOverview.detected ||\n result.aiMode.detected\n}\n\nfunction withResultQuality(result: HarvestResult): HarvestResult {\n const hasUsableContent = resultHasUsableContent(result)\n const hasWarnings = (result.diagnostics.warnings?.length ?? 0) > 0\n const resultQuality = hasUsableContent\n ? (hasWarnings ? 'partial' : 'complete')\n : 'degraded'\n const degradedResult = resultQuality === 'degraded'\n\n result.diagnostics = {\n ...result.diagnostics,\n resultQuality,\n degradedResult,\n degradationReasons: degradedResult ? ['empty_primary_serp'] : [],\n retryRecommended: degradedResult,\n }\n return result\n}\n\nfunction degradedResultMessage(result: HarvestResult): string {\n const reasons = result.diagnostics.degradationReasons ?? []\n if (reasons.includes('empty_primary_serp')) {\n return 'No usable primary SERP surface was captured; the result may be incomplete.'\n }\n return 'The harvest result was marked degraded.'\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction maxAttemptsForProxyMode(proxyMode: ProxyMode): number {\n return proxyMode === 'location' ? LOCATION_PROXY_MAX_ATTEMPTS : MAX_ATTEMPTS\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\nfunction retryableLocationProxyError(outcome: HarvestAttemptOutcome): boolean {\n return outcome === 'captcha' || outcome === 'proxy_tunnel_failed' || outcome === 'proxy_unavailable'\n}\n\nfunction locationMismatchMessage(result: HarvestResult): string {\n const evidence = result.diagnostics.debug?.locationEvidence\n const expected = evidence?.expected?.canonicalLocation ?? result.location ?? 'requested location'\n const candidates = evidence?.candidates\n .slice(0, 3)\n .map(candidate => `${candidate.city}, ${candidate.regionCode}`)\n .join('; ')\n return candidates\n ? `Google returned results for ${candidates}, not ${expected}`\n : `Google returned results for a different location than ${expected}`\n}\n\nfunction shouldRetryLocationMismatch(result: HarvestResult, proxyMode: ProxyMode): boolean {\n return proxyMode === 'location' && result.diagnostics.debug?.locationEvidence?.status === 'mismatch'\n}\n\nfunction stripInternalDebug(result: HarvestResult, keepDebug: boolean): HarvestResult {\n if (keepDebug || !result.diagnostics.debug) return result\n const diagnostics = { ...result.diagnostics }\n delete diagnostics.debug\n return { ...result, diagnostics }\n}\n\nasync function cleanupDisposableProxy(kernelApiKey: string | undefined, proxyId: string | undefined): Promise<void> {\n if (!kernelApiKey || !proxyId) return\n try {\n await deleteKernelProxyId(kernelApiKey, proxyId)\n console.info(JSON.stringify({\n event: 'kernel_proxy_deleted',\n proxy_id_suffix: proxyId.slice(-6),\n }))\n } catch (err) {\n console.warn(JSON.stringify({\n event: 'kernel_proxy_delete_failed',\n proxy_id_suffix: proxyId.slice(-6),\n message: errorMessage(err),\n }))\n }\n}\n\nasync function extractOnce(options: HarvestOptions, signal?: AbortSignal): Promise<ExtractOnceOutcome> {\n const driver = new BrowserDriver()\n const reporter = new ProgressReporter()\n const extractor = new PAAExtractor(driver, reporter)\n if (signal?.aborted) {\n return {\n result: null,\n error: abortReason(signal),\n cleanup: await driver.close(),\n debug: null,\n }\n }\n\n let onAbort: (() => void) | undefined\n const abortPromise = signal\n ? new Promise<never>((_, reject) => {\n onAbort = () => reject(abortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n })\n : null\n\n let result: HarvestResult | null = null\n let error: unknown = null\n let cleanup: BrowserCloseResult\n let debug: HarvestDebugSnapshot | null = null\n try {\n const extraction = extractor.extract(options, signal)\n if (abortPromise) extraction.catch(() => {})\n result = await (abortPromise ? Promise.race([extraction, abortPromise]) : extraction)\n } catch (err) {\n error = err\n } finally {\n if (signal && onAbort) signal.removeEventListener('abort', onAbort)\n debug = result?.diagnostics.debug ?? (options.debug ? {\n enabled: true,\n request: {\n query: options.query,\n locationInput: options.location ?? null,\n canonicalLocation: null,\n uule: null,\n gl: options.gl,\n hl: options.hl,\n device: options.device,\n proxyMode: options.proxyMode,\n proxyZip: options.proxyZip ?? null,\n serpOnly: options.serpOnly,\n pages: options.pages ?? 1,\n },\n browser: driver.getDebugSnapshot(),\n } : null)\n cleanup = await driver.close()\n }\n if (error) {\n const outcome = classifyAttemptError(error)\n const budgetExhausted = outcome === 'timeout' || outcome === 'request_aborted'\n let salvaged: HarvestResult | null = null\n if (budgetExhausted) {\n try {\n salvaged = typeof extractor.getPartialResult === 'function' ? extractor.getPartialResult() : null\n } catch {\n salvaged = null\n }\n }\n if (salvaged) {\n return { result: salvaged, error: null, cleanup: cleanup!, debug, salvagedFrom: error }\n }\n return { result: null, error, cleanup: cleanup!, debug }\n }\n return { result: result!, error: null, cleanup: cleanup!, debug }\n}\n\nexport async function harvest(rawOptions: unknown): Promise<HarvestResult> {\n const raw = typeof rawOptions === 'object' && rawOptions !== null ? rawOptions : {}\n const signal = getAbortSignal(rawOptions)\n const onAttemptEvent = getAttemptLogSink(rawOptions)\n const requestedProxyMode = (raw as { proxyMode?: unknown }).proxyMode\n const proxyMode = requestedProxyMode === 'location' || requestedProxyMode === 'none' || requestedProxyMode === 'configured'\n ? requestedProxyMode\n : DEFAULT_PROXY_MODE\n const kernelApiKey = typeof (raw as { kernelApiKey?: unknown }).kernelApiKey === 'string'\n ? ((raw as { kernelApiKey: string }).kernelApiKey).trim()\n : browserServiceApiKey()\n const configuredKernelProxyId = typeof (raw as { kernelProxyId?: unknown }).kernelProxyId === 'string'\n ? ((raw as { kernelProxyId: string }).kernelProxyId).trim()\n : browserServiceProxyId()\n const proxyOpts = {\n kernelApiKey,\n proxyMode: proxyMode as ProxyMode,\n configuredKernelProxyId,\n location: typeof (raw as { location?: unknown }).location === 'string' ? (raw as { location: string }).location : undefined,\n proxyZip: typeof (raw as { proxyZip?: unknown }).proxyZip === 'string' ? (raw as { proxyZip: string }).proxyZip : undefined,\n gl: typeof (raw as { gl?: unknown }).gl === 'string' ? (raw as { gl: string }).gl : 'us',\n }\n const requestedDebug = typeof (raw as { debug?: unknown }).debug === 'boolean'\n ? (raw as { debug: boolean }).debug\n : false\n const needsLocationEvidence = proxyMode === 'location' && Boolean(proxyOpts.location)\n const requestedMaxAttempts = (raw as { maxAttempts?: unknown }).maxAttempts\n const baseMaxAttempts = typeof requestedMaxAttempts === 'number' && Number.isInteger(requestedMaxAttempts) && requestedMaxAttempts >= 1\n ? requestedMaxAttempts\n : maxAttemptsForProxyMode(proxyMode as ProxyMode) + 1\n const maxAttempts = baseMaxAttempts + (backupProxyAvailable() ? BACKUP_LAST_RESORT_ATTEMPTS : 0)\n const softDeadlineMs = typeof (raw as { softDeadlineMs?: unknown }).softDeadlineMs === 'number'\n ? (raw as { softDeadlineMs: number }).softDeadlineMs\n : null\n const serializer = new OutputSerializer()\n let lastError: unknown = null\n let effectiveProxyMode: ProxyMode = proxyMode as ProxyMode\n let proxyFailures = 0\n\n for (let i = 0; i < maxAttempts; i++) {\n const attemptNumber = i + 1\n const startedAtMs = Date.now()\n try {\n if (signal?.aborted) throw abortReason(signal)\n const isBackupAttempt = i >= baseMaxAttempts\n if (isBackupAttempt && softDeadlineMs !== null && Date.now() >= softDeadlineMs) break\n const resolution = isBackupAttempt\n ? await resolveBackupKernelProxyId({ kernelApiKey, proxyMode: effectiveProxyMode, attemptIndex: i })\n : await resolveKernelProxyId({\n ...proxyOpts,\n proxyMode: effectiveProxyMode,\n attemptIndex: i,\n fresh: effectiveProxyMode === 'location',\n backupOnExhaustion: true,\n })\n if (!resolution) break\n const mergedAttempt = {\n ...raw,\n kernelApiKey,\n kernelProxyId: resolution.kernelProxyId,\n kernelProxyResolution: resolution.resolution,\n proxyMode: effectiveProxyMode,\n debug: requestedDebug || needsLocationEvidence,\n }\n if (effectiveProxyMode === 'none' && !isBackupAttempt) (mergedAttempt as Record<string, unknown>).kernelProxyId = undefined\n const attemptOptions = HarvestOptionsSchema.parse(mergedAttempt)\n await emitAttemptEvent(onAttemptEvent, {\n type: 'started',\n attemptNumber,\n maxAttempts,\n query: attemptOptions.query,\n location: attemptOptions.location ?? null,\n maxQuestions: attemptOptions.maxQuestions,\n startedAt: new Date(startedAtMs).toISOString(),\n })\n console.info(JSON.stringify({\n event: 'harvest_attempt_started',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n query: attemptOptions.query,\n location: attemptOptions.location ?? null,\n max_questions: attemptOptions.maxQuestions,\n }))\n const baseCtx = currentCostContext()\n const attempt = baseCtx\n ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal))\n : await extractOnce(attemptOptions, signal)\n if (attempt.error) {\n const err = attempt.error\n const outcome = classifyAttemptError(err)\n if ((outcome === 'captcha' || outcome === 'proxy_tunnel_failed') && effectiveProxyMode === 'location') {\n proxyFailures++\n if (proxyFailures >= 2) effectiveProxyMode = 'none'\n }\n const willRetry = i < maxAttempts - 1 && (\n outcome === 'captcha' || (proxyMode === 'location' && retryableLocationProxyError(outcome))\n )\n if (outcome === 'captcha') {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_captcha',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n } else if (willRetry) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_proxy_retry',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n outcome,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n }\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome,\n kernelSessionId: attempt.cleanup.kernelSessionId,\n questionCount: 0,\n durationMs: Date.now() - startedAtMs,\n error: errorMessage(err),\n willRetry,\n cleanup: attempt.cleanup,\n debug: attempt.debug,\n completedAt: new Date().toISOString(),\n })\n await cleanupDisposableProxy(kernelApiKey, resolution.disposableProxyId)\n lastError = err\n if (willRetry) continue\n break\n }\n const result = attempt.result\n if (!result) throw new Error('Harvest attempt completed without a result')\n if (shouldRetryLocationMismatch(result, proxyMode as ProxyMode)) {\n const err = new LocationMismatchError(locationMismatchMessage(result))\n const willRetry = i < maxAttempts - 1\n console.warn(JSON.stringify({\n event: 'harvest_attempt_location_mismatch',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n message: err.message,\n will_retry: willRetry,\n }))\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome: 'location_mismatch',\n kernelSessionId: attempt.cleanup.kernelSessionId,\n questionCount: result.totalQuestions,\n durationMs: Date.now() - startedAtMs,\n error: err.message,\n willRetry,\n cleanup: attempt.cleanup,\n debug: attempt.debug,\n completedAt: new Date().toISOString(),\n })\n await cleanupDisposableProxy(kernelApiKey, resolution.disposableProxyId)\n lastError = err\n if (willRetry) continue\n break\n }\n const finalResult = withResultQuality(stripInternalDebug(result, requestedDebug))\n const outcome = classifyAttemptResult(finalResult)\n const willRetry = outcome === 'degraded_result' && i < maxAttempts - 1\n const resultError = outcome === 'degraded_result' ? degradedResultMessage(finalResult) : null\n if (willRetry) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_degraded_result',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n degradation_reasons: finalResult.diagnostics.degradationReasons,\n will_retry: true,\n }))\n }\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome,\n kernelSessionId: attempt.cleanup.kernelSessionId,\n questionCount: finalResult.totalQuestions,\n durationMs: Date.now() - startedAtMs,\n error: resultError,\n willRetry,\n cleanup: attempt.cleanup,\n debug: attempt.debug,\n completedAt: new Date().toISOString(),\n })\n await cleanupDisposableProxy(kernelApiKey, resolution.disposableProxyId)\n if (willRetry) continue\n if (attemptOptions.format === 'json' || attemptOptions.format === 'both') {\n await serializer.writeJSON(finalResult, attemptOptions.outputDir)\n }\n if (attemptOptions.format === 'csv' || attemptOptions.format === 'both') {\n await Promise.all([\n serializer.writeCSV(finalResult.flat, attemptOptions.outputDir),\n finalResult.videos.length > 0 ? serializer.writeVideoCSV(finalResult.videos, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.forums.length > 0 ? serializer.writeForumCSV(finalResult.forums, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.aiOverview.detected ? serializer.writeAIOverviewCSV(finalResult.aiOverview.citations, finalResult.aiOverview.text, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.aiMode.detected ? serializer.writeAIModeCSV(finalResult.aiMode.citations, finalResult.aiMode.text, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n finalResult.whatPeopleSaying.length > 0 ? serializer.writeWhatPeopleSayingCSV(finalResult.whatPeopleSaying, finalResult.seed, attemptOptions.outputDir) : Promise.resolve(''),\n ])\n }\n return finalResult\n } catch (err) {\n const outcome = classifyAttemptError(err)\n if ((outcome === 'captcha' || outcome === 'proxy_tunnel_failed') && effectiveProxyMode === 'location') {\n proxyFailures++\n if (proxyFailures >= 2) effectiveProxyMode = 'none'\n }\n const willRetry = i < maxAttempts - 1 && (\n outcome === 'captcha' || (proxyMode === 'location' && retryableLocationProxyError(outcome))\n )\n if (outcome === 'captcha') {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_captcha',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n } else if (willRetry) {\n console.warn(JSON.stringify({\n event: 'harvest_attempt_proxy_retry',\n attempt_number: attemptNumber,\n max_attempts: maxAttempts,\n outcome,\n message: errorMessage(err),\n will_retry: willRetry,\n }))\n }\n await emitAttemptEvent(onAttemptEvent, {\n type: 'finished',\n attemptNumber,\n maxAttempts,\n outcome,\n kernelSessionId: null,\n questionCount: 0,\n durationMs: Date.now() - startedAtMs,\n error: errorMessage(err),\n willRetry,\n cleanup: {\n kernelSessionId: null,\n kernelDeleteStarted: false,\n kernelDeleteSucceeded: null,\n kernelDeleteError: null,\n browserCloseSucceeded: null,\n browserCloseError: null,\n },\n debug: null,\n completedAt: new Date().toISOString(),\n })\n lastError = err\n if (willRetry) continue\n if (outcome === 'captcha') break\n throw err\n }\n }\n if (lastError && !(lastError instanceof CaptchaError)) throw lastError\n console.warn(JSON.stringify({\n event: 'harvest_captcha_exhausted',\n max_attempts: maxAttempts,\n session_kind: kernelApiKey ? 'kernel' : 'local',\n }))\n throw new CaptchaError(sanitizeVendorName(`CAPTCHA on all ${maxAttempts} fresh sessions. Try again in a few minutes.`))\n}\n","#!/usr/bin/env node\nimport { runCli } from '../src/cli.js'\nrunCli()\n"],"mappings":"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","names":["import_node_crypto","import_zod","StealthPlugin","Kernel","playwrightChromium","DESKTOP_USER_AGENT","MOBILE_USER_AGENT","s","organicResults","localPack","rawEntityIds","entityIds","aiSurfaces","locationEvidence","allOrganic","stats","fs","path","Papa","import_sdk","import_node_crypto","import_sdk","Kernel","proxyIdSuffix","errorText","Kernel","Kernel","createErrors","errorText","resolution","outcome","willRetry"]}