@iflow-mcp/jakeliume-webpeel 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +313 -0
- package/dist/cache.d.ts +30 -0
- package/dist/cache.js +139 -0
- package/dist/cli/commands/auth.d.ts +5 -0
- package/dist/cli/commands/auth.js +411 -0
- package/dist/cli/commands/doctor.d.ts +37 -0
- package/dist/cli/commands/doctor.js +371 -0
- package/dist/cli/commands/fetch.d.ts +6 -0
- package/dist/cli/commands/fetch.js +1345 -0
- package/dist/cli/commands/guide.d.ts +2 -0
- package/dist/cli/commands/guide.js +183 -0
- package/dist/cli/commands/interact.d.ts +5 -0
- package/dist/cli/commands/interact.js +840 -0
- package/dist/cli/commands/jobs.d.ts +5 -0
- package/dist/cli/commands/jobs.js +997 -0
- package/dist/cli/commands/monitor.d.ts +12 -0
- package/dist/cli/commands/monitor.js +197 -0
- package/dist/cli/commands/observe.d.ts +12 -0
- package/dist/cli/commands/observe.js +158 -0
- package/dist/cli/commands/screenshot.d.ts +5 -0
- package/dist/cli/commands/screenshot.js +282 -0
- package/dist/cli/commands/search.d.ts +5 -0
- package/dist/cli/commands/search.js +1021 -0
- package/dist/cli/commands/setup.d.ts +13 -0
- package/dist/cli/commands/setup.js +244 -0
- package/dist/cli/commands/skill.d.ts +15 -0
- package/dist/cli/commands/skill.js +195 -0
- package/dist/cli/utils.d.ts +84 -0
- package/dist/cli/utils.js +806 -0
- package/dist/cli-auth.d.ts +75 -0
- package/dist/cli-auth.js +369 -0
- package/dist/cli.d.ts +17 -0
- package/dist/cli.js +99 -0
- package/dist/core/actions.d.ts +69 -0
- package/dist/core/actions.js +495 -0
- package/dist/core/agent.d.ts +98 -0
- package/dist/core/agent.js +558 -0
- package/dist/core/answer.d.ts +42 -0
- package/dist/core/answer.js +395 -0
- package/dist/core/application-tracker.d.ts +84 -0
- package/dist/core/application-tracker.js +184 -0
- package/dist/core/apply.d.ts +162 -0
- package/dist/core/apply.js +816 -0
- package/dist/core/auth-detection.d.ts +35 -0
- package/dist/core/auth-detection.js +358 -0
- package/dist/core/auto-extract.d.ts +82 -0
- package/dist/core/auto-extract.js +604 -0
- package/dist/core/auto-interact.d.ts +23 -0
- package/dist/core/auto-interact.js +246 -0
- package/dist/core/bm25-filter.d.ts +66 -0
- package/dist/core/bm25-filter.js +288 -0
- package/dist/core/branding.d.ts +54 -0
- package/dist/core/branding.js +234 -0
- package/dist/core/browser-fetch.d.ts +323 -0
- package/dist/core/browser-fetch.js +1600 -0
- package/dist/core/browser-pool.d.ts +91 -0
- package/dist/core/browser-pool.js +550 -0
- package/dist/core/budget.d.ts +42 -0
- package/dist/core/budget.js +324 -0
- package/dist/core/business-intel.d.ts +47 -0
- package/dist/core/business-intel.js +279 -0
- package/dist/core/cache.d.ts +13 -0
- package/dist/core/cache.js +121 -0
- package/dist/core/cf-worker-proxy.d.ts +32 -0
- package/dist/core/cf-worker-proxy.js +87 -0
- package/dist/core/challenge-detection.d.ts +26 -0
- package/dist/core/challenge-detection.js +468 -0
- package/dist/core/change-tracking.d.ts +75 -0
- package/dist/core/change-tracking.js +276 -0
- package/dist/core/chunker.d.ts +46 -0
- package/dist/core/chunker.js +249 -0
- package/dist/core/chunking.d.ts +42 -0
- package/dist/core/chunking.js +181 -0
- package/dist/core/circuit-breaker.d.ts +44 -0
- package/dist/core/circuit-breaker.js +85 -0
- package/dist/core/content-pruner.d.ts +47 -0
- package/dist/core/content-pruner.js +425 -0
- package/dist/core/cookie-cache.d.ts +60 -0
- package/dist/core/cookie-cache.js +163 -0
- package/dist/core/crawl-checkpoint.d.ts +54 -0
- package/dist/core/crawl-checkpoint.js +104 -0
- package/dist/core/crawler.d.ts +84 -0
- package/dist/core/crawler.js +349 -0
- package/dist/core/cross-verify.d.ts +27 -0
- package/dist/core/cross-verify.js +93 -0
- package/dist/core/deep-fetch.d.ts +74 -0
- package/dist/core/deep-fetch.js +405 -0
- package/dist/core/deep-research.d.ts +141 -0
- package/dist/core/deep-research.js +972 -0
- package/dist/core/design-analysis.d.ts +70 -0
- package/dist/core/design-analysis.js +490 -0
- package/dist/core/design-compare.d.ts +38 -0
- package/dist/core/design-compare.js +264 -0
- package/dist/core/diff.d.ts +61 -0
- package/dist/core/diff.js +289 -0
- package/dist/core/dns-cache.d.ts +20 -0
- package/dist/core/dns-cache.js +198 -0
- package/dist/core/documents.d.ts +23 -0
- package/dist/core/documents.js +123 -0
- package/dist/core/domain-memory.d.ts +66 -0
- package/dist/core/domain-memory.js +163 -0
- package/dist/core/domain-verify.d.ts +40 -0
- package/dist/core/domain-verify.js +379 -0
- package/dist/core/engine-ranker.d.ts +112 -0
- package/dist/core/engine-ranker.js +395 -0
- package/dist/core/extract-inline.d.ts +38 -0
- package/dist/core/extract-inline.js +215 -0
- package/dist/core/extract-listings.d.ts +38 -0
- package/dist/core/extract-listings.js +461 -0
- package/dist/core/extract.d.ts +9 -0
- package/dist/core/extract.js +139 -0
- package/dist/core/fetch-cache.d.ts +57 -0
- package/dist/core/fetch-cache.js +95 -0
- package/dist/core/fetcher.d.ts +13 -0
- package/dist/core/fetcher.js +12 -0
- package/dist/core/google-cache.d.ts +29 -0
- package/dist/core/google-cache.js +180 -0
- package/dist/core/google-serp-parser.d.ts +82 -0
- package/dist/core/google-serp-parser.js +287 -0
- package/dist/core/hotel-search.d.ts +122 -0
- package/dist/core/hotel-search.js +382 -0
- package/dist/core/http-fetch.d.ts +72 -0
- package/dist/core/http-fetch.js +820 -0
- package/dist/core/human.d.ts +175 -0
- package/dist/core/human.js +680 -0
- package/dist/core/image-caption.d.ts +44 -0
- package/dist/core/image-caption.js +271 -0
- package/dist/core/jobs.d.ts +75 -0
- package/dist/core/jobs.js +634 -0
- package/dist/core/json-ld.d.ts +15 -0
- package/dist/core/json-ld.js +617 -0
- package/dist/core/language-detect.d.ts +18 -0
- package/dist/core/language-detect.js +135 -0
- package/dist/core/links.d.ts +10 -0
- package/dist/core/links.js +44 -0
- package/dist/core/llm-extract.d.ts +71 -0
- package/dist/core/llm-extract.js +507 -0
- package/dist/core/llm-provider.d.ts +100 -0
- package/dist/core/llm-provider.js +702 -0
- package/dist/core/local-search.d.ts +60 -0
- package/dist/core/local-search.js +308 -0
- package/dist/core/logger.d.ts +28 -0
- package/dist/core/logger.js +104 -0
- package/dist/core/map.d.ts +33 -0
- package/dist/core/map.js +127 -0
- package/dist/core/markdown.d.ts +92 -0
- package/dist/core/markdown.js +809 -0
- package/dist/core/metadata.d.ts +34 -0
- package/dist/core/metadata.js +422 -0
- package/dist/core/observe.d.ts +113 -0
- package/dist/core/observe.js +395 -0
- package/dist/core/ocr.d.ts +12 -0
- package/dist/core/ocr.js +33 -0
- package/dist/core/paginate.d.ts +31 -0
- package/dist/core/paginate.js +106 -0
- package/dist/core/pdf.d.ts +8 -0
- package/dist/core/pdf.js +25 -0
- package/dist/core/peel-tls.d.ts +25 -0
- package/dist/core/peel-tls.js +220 -0
- package/dist/core/pipeline.d.ts +132 -0
- package/dist/core/pipeline.js +1666 -0
- package/dist/core/profiles.d.ts +61 -0
- package/dist/core/profiles.js +350 -0
- package/dist/core/prompt-guard.d.ts +30 -0
- package/dist/core/prompt-guard.js +119 -0
- package/dist/core/proxy-config.d.ts +90 -0
- package/dist/core/proxy-config.js +172 -0
- package/dist/core/quick-answer.d.ts +53 -0
- package/dist/core/quick-answer.js +833 -0
- package/dist/core/rate-governor.d.ts +80 -0
- package/dist/core/rate-governor.js +238 -0
- package/dist/core/readability.d.ts +57 -0
- package/dist/core/readability.js +533 -0
- package/dist/core/research.d.ts +66 -0
- package/dist/core/research.js +270 -0
- package/dist/core/retry.d.ts +60 -0
- package/dist/core/retry.js +119 -0
- package/dist/core/safe-browsing.d.ts +30 -0
- package/dist/core/safe-browsing.js +206 -0
- package/dist/core/schema-extraction.d.ts +66 -0
- package/dist/core/schema-extraction.js +352 -0
- package/dist/core/schema-postprocess.d.ts +32 -0
- package/dist/core/schema-postprocess.js +469 -0
- package/dist/core/schema-templates.d.ts +19 -0
- package/dist/core/schema-templates.js +143 -0
- package/dist/core/screenshot.d.ts +224 -0
- package/dist/core/screenshot.js +207 -0
- package/dist/core/search-engines.d.ts +25 -0
- package/dist/core/search-engines.js +182 -0
- package/dist/core/search-provider.d.ts +243 -0
- package/dist/core/search-provider.js +1629 -0
- package/dist/core/searxng-provider.d.ts +35 -0
- package/dist/core/searxng-provider.js +105 -0
- package/dist/core/selective-evidence.d.ts +151 -0
- package/dist/core/selective-evidence.js +389 -0
- package/dist/core/site-search.d.ts +44 -0
- package/dist/core/site-search.js +252 -0
- package/dist/core/sitemap.d.ts +23 -0
- package/dist/core/sitemap.js +105 -0
- package/dist/core/source-credibility.d.ts +29 -0
- package/dist/core/source-credibility.js +584 -0
- package/dist/core/source-scoring.d.ts +166 -0
- package/dist/core/source-scoring.js +396 -0
- package/dist/core/stemmer.d.ts +38 -0
- package/dist/core/stemmer.js +509 -0
- package/dist/core/strategies.d.ts +104 -0
- package/dist/core/strategies.js +1044 -0
- package/dist/core/strategy-hooks.d.ts +145 -0
- package/dist/core/strategy-hooks.js +74 -0
- package/dist/core/structured-extract.d.ts +43 -0
- package/dist/core/structured-extract.js +550 -0
- package/dist/core/summarize.d.ts +17 -0
- package/dist/core/summarize.js +78 -0
- package/dist/core/synonyms.d.ts +42 -0
- package/dist/core/synonyms.js +184 -0
- package/dist/core/system-monitor.d.ts +61 -0
- package/dist/core/system-monitor.js +133 -0
- package/dist/core/table-format.d.ts +30 -0
- package/dist/core/table-format.js +146 -0
- package/dist/core/threat-feeds.d.ts +23 -0
- package/dist/core/threat-feeds.js +104 -0
- package/dist/core/timing.d.ts +21 -0
- package/dist/core/timing.js +33 -0
- package/dist/core/transcript-export.d.ts +47 -0
- package/dist/core/transcript-export.js +107 -0
- package/dist/core/user-agents.d.ts +82 -0
- package/dist/core/user-agents.js +239 -0
- package/dist/core/vertical-search.d.ts +54 -0
- package/dist/core/vertical-search.js +158 -0
- package/dist/core/watch-manager.d.ts +175 -0
- package/dist/core/watch-manager.js +416 -0
- package/dist/core/watch.d.ts +101 -0
- package/dist/core/watch.js +389 -0
- package/dist/core/youtube.d.ts +130 -0
- package/dist/core/youtube.js +1175 -0
- package/dist/ee/challenge-re-export.d.ts +1 -0
- package/dist/ee/challenge-re-export.js +1 -0
- package/dist/ee/challenge-solver.d.ts +72 -0
- package/dist/ee/challenge-solver.js +720 -0
- package/dist/ee/domain-extractors.d.ts +8 -0
- package/dist/ee/domain-extractors.js +8 -0
- package/dist/ee/domain-intel.d.ts +16 -0
- package/dist/ee/domain-intel.js +133 -0
- package/dist/ee/extractors/allrecipes.d.ts +2 -0
- package/dist/ee/extractors/allrecipes.js +120 -0
- package/dist/ee/extractors/amazon.d.ts +2 -0
- package/dist/ee/extractors/amazon.js +78 -0
- package/dist/ee/extractors/arxiv.d.ts +2 -0
- package/dist/ee/extractors/arxiv.js +137 -0
- package/dist/ee/extractors/bestbuy.d.ts +2 -0
- package/dist/ee/extractors/bestbuy.js +78 -0
- package/dist/ee/extractors/carscom.d.ts +2 -0
- package/dist/ee/extractors/carscom.js +121 -0
- package/dist/ee/extractors/coingecko.d.ts +2 -0
- package/dist/ee/extractors/coingecko.js +134 -0
- package/dist/ee/extractors/craigslist.d.ts +2 -0
- package/dist/ee/extractors/craigslist.js +92 -0
- package/dist/ee/extractors/devto.d.ts +2 -0
- package/dist/ee/extractors/devto.js +135 -0
- package/dist/ee/extractors/ebay.d.ts +2 -0
- package/dist/ee/extractors/ebay.js +90 -0
- package/dist/ee/extractors/espn.d.ts +2 -0
- package/dist/ee/extractors/espn.js +260 -0
- package/dist/ee/extractors/etsy.d.ts +2 -0
- package/dist/ee/extractors/etsy.js +52 -0
- package/dist/ee/extractors/facebook.d.ts +2 -0
- package/dist/ee/extractors/facebook.js +46 -0
- package/dist/ee/extractors/github.d.ts +2 -0
- package/dist/ee/extractors/github.js +196 -0
- package/dist/ee/extractors/google-flights.d.ts +2 -0
- package/dist/ee/extractors/google-flights.js +176 -0
- package/dist/ee/extractors/hackernews.d.ts +2 -0
- package/dist/ee/extractors/hackernews.js +147 -0
- package/dist/ee/extractors/imdb.d.ts +2 -0
- package/dist/ee/extractors/imdb.js +172 -0
- package/dist/ee/extractors/index.d.ts +26 -0
- package/dist/ee/extractors/index.js +247 -0
- package/dist/ee/extractors/instagram.d.ts +2 -0
- package/dist/ee/extractors/instagram.js +102 -0
- package/dist/ee/extractors/kalshi.d.ts +2 -0
- package/dist/ee/extractors/kalshi.js +121 -0
- package/dist/ee/extractors/kayak-cars.d.ts +2 -0
- package/dist/ee/extractors/kayak-cars.js +270 -0
- package/dist/ee/extractors/linkedin.d.ts +2 -0
- package/dist/ee/extractors/linkedin.js +113 -0
- package/dist/ee/extractors/medium.d.ts +2 -0
- package/dist/ee/extractors/medium.js +130 -0
- package/dist/ee/extractors/news.d.ts +4 -0
- package/dist/ee/extractors/news.js +173 -0
- package/dist/ee/extractors/npm.d.ts +2 -0
- package/dist/ee/extractors/npm.js +86 -0
- package/dist/ee/extractors/pdf.d.ts +2 -0
- package/dist/ee/extractors/pdf.js +108 -0
- package/dist/ee/extractors/pinterest.d.ts +2 -0
- package/dist/ee/extractors/pinterest.js +34 -0
- package/dist/ee/extractors/polymarket.d.ts +2 -0
- package/dist/ee/extractors/polymarket.js +358 -0
- package/dist/ee/extractors/producthunt.d.ts +2 -0
- package/dist/ee/extractors/producthunt.js +88 -0
- package/dist/ee/extractors/pubmed.d.ts +2 -0
- package/dist/ee/extractors/pubmed.js +162 -0
- package/dist/ee/extractors/pypi.d.ts +2 -0
- package/dist/ee/extractors/pypi.js +80 -0
- package/dist/ee/extractors/reddit.d.ts +2 -0
- package/dist/ee/extractors/reddit.js +438 -0
- package/dist/ee/extractors/redfin.d.ts +2 -0
- package/dist/ee/extractors/redfin.js +156 -0
- package/dist/ee/extractors/semanticscholar.d.ts +2 -0
- package/dist/ee/extractors/semanticscholar.js +131 -0
- package/dist/ee/extractors/shared.d.ts +12 -0
- package/dist/ee/extractors/shared.js +76 -0
- package/dist/ee/extractors/soundcloud.d.ts +2 -0
- package/dist/ee/extractors/soundcloud.js +34 -0
- package/dist/ee/extractors/sportsbetting.d.ts +2 -0
- package/dist/ee/extractors/sportsbetting.js +37 -0
- package/dist/ee/extractors/spotify.d.ts +2 -0
- package/dist/ee/extractors/spotify.js +34 -0
- package/dist/ee/extractors/stackoverflow.d.ts +2 -0
- package/dist/ee/extractors/stackoverflow.js +61 -0
- package/dist/ee/extractors/substack.d.ts +2 -0
- package/dist/ee/extractors/substack.js +115 -0
- package/dist/ee/extractors/substackroot.d.ts +2 -0
- package/dist/ee/extractors/substackroot.js +46 -0
- package/dist/ee/extractors/tiktok.d.ts +2 -0
- package/dist/ee/extractors/tiktok.js +29 -0
- package/dist/ee/extractors/tradingview.d.ts +2 -0
- package/dist/ee/extractors/tradingview.js +182 -0
- package/dist/ee/extractors/twitch.d.ts +2 -0
- package/dist/ee/extractors/twitch.js +36 -0
- package/dist/ee/extractors/twitter.d.ts +2 -0
- package/dist/ee/extractors/twitter.js +327 -0
- package/dist/ee/extractors/types.d.ts +14 -0
- package/dist/ee/extractors/types.js +1 -0
- package/dist/ee/extractors/walmart.d.ts +2 -0
- package/dist/ee/extractors/walmart.js +50 -0
- package/dist/ee/extractors/weather.d.ts +2 -0
- package/dist/ee/extractors/weather.js +133 -0
- package/dist/ee/extractors/wikipedia.d.ts +4 -0
- package/dist/ee/extractors/wikipedia.js +235 -0
- package/dist/ee/extractors/yelp.d.ts +2 -0
- package/dist/ee/extractors/yelp.js +216 -0
- package/dist/ee/extractors/youtube.d.ts +2 -0
- package/dist/ee/extractors/youtube.js +189 -0
- package/dist/ee/extractors/zillow.d.ts +54 -0
- package/dist/ee/extractors/zillow.js +247 -0
- package/dist/ee/extractors-re-export.d.ts +1 -0
- package/dist/ee/extractors-re-export.js +1 -0
- package/dist/ee/premium-hooks.d.ts +20 -0
- package/dist/ee/premium-hooks.js +50 -0
- package/dist/ee/spa-detection.d.ts +2 -0
- package/dist/ee/spa-detection.js +2 -0
- package/dist/ee/stability.d.ts +4 -0
- package/dist/ee/stability.js +29 -0
- package/dist/ee/swr-cache.d.ts +14 -0
- package/dist/ee/swr-cache.js +34 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +291 -0
- package/dist/integrations/index.d.ts +2 -0
- package/dist/integrations/index.js +2 -0
- package/dist/integrations/langchain.d.ts +64 -0
- package/dist/integrations/langchain.js +115 -0
- package/dist/integrations/llamaindex.d.ts +50 -0
- package/dist/integrations/llamaindex.js +91 -0
- package/dist/mcp/handlers/act.d.ts +5 -0
- package/dist/mcp/handlers/act.js +34 -0
- package/dist/mcp/handlers/definitions.d.ts +6 -0
- package/dist/mcp/handlers/definitions.js +395 -0
- package/dist/mcp/handlers/extract.d.ts +7 -0
- package/dist/mcp/handlers/extract.js +135 -0
- package/dist/mcp/handlers/fetch.d.ts +6 -0
- package/dist/mcp/handlers/fetch.js +98 -0
- package/dist/mcp/handlers/find.d.ts +5 -0
- package/dist/mcp/handlers/find.js +137 -0
- package/dist/mcp/handlers/index.d.ts +13 -0
- package/dist/mcp/handlers/index.js +63 -0
- package/dist/mcp/handlers/legacy.d.ts +25 -0
- package/dist/mcp/handlers/legacy.js +450 -0
- package/dist/mcp/handlers/meta.d.ts +6 -0
- package/dist/mcp/handlers/meta.js +40 -0
- package/dist/mcp/handlers/monitor.d.ts +5 -0
- package/dist/mcp/handlers/monitor.js +41 -0
- package/dist/mcp/handlers/observe.d.ts +8 -0
- package/dist/mcp/handlers/observe.js +37 -0
- package/dist/mcp/handlers/read.d.ts +6 -0
- package/dist/mcp/handlers/read.js +78 -0
- package/dist/mcp/handlers/see.d.ts +5 -0
- package/dist/mcp/handlers/see.js +75 -0
- package/dist/mcp/handlers/types.d.ts +29 -0
- package/dist/mcp/handlers/types.js +28 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +108 -0
- package/dist/mcp/smart-router.d.ts +23 -0
- package/dist/mcp/smart-router.js +178 -0
- package/dist/server/app.d.ts +14 -0
- package/dist/server/app.js +632 -0
- package/dist/server/auth-store.d.ts +28 -0
- package/dist/server/auth-store.js +88 -0
- package/dist/server/bull-queues.d.ts +60 -0
- package/dist/server/bull-queues.js +90 -0
- package/dist/server/email-service.d.ts +55 -0
- package/dist/server/email-service.js +291 -0
- package/dist/server/job-queue.d.ts +100 -0
- package/dist/server/job-queue.js +145 -0
- package/dist/server/logger.d.ts +10 -0
- package/dist/server/logger.js +37 -0
- package/dist/server/middleware/audit-log.d.ts +14 -0
- package/dist/server/middleware/audit-log.js +73 -0
- package/dist/server/middleware/auth.d.ts +35 -0
- package/dist/server/middleware/auth.js +225 -0
- package/dist/server/middleware/rate-limit.d.ts +50 -0
- package/dist/server/middleware/rate-limit.js +270 -0
- package/dist/server/middleware/scope-guard.d.ts +25 -0
- package/dist/server/middleware/scope-guard.js +45 -0
- package/dist/server/middleware/url-validator.d.ts +15 -0
- package/dist/server/middleware/url-validator.js +201 -0
- package/dist/server/openapi.yaml +6418 -0
- package/dist/server/pg-auth-store.d.ts +146 -0
- package/dist/server/pg-auth-store.js +576 -0
- package/dist/server/pg-job-queue.d.ts +59 -0
- package/dist/server/pg-job-queue.js +375 -0
- package/dist/server/routes/activity.d.ts +6 -0
- package/dist/server/routes/activity.js +79 -0
- package/dist/server/routes/admin-active.d.ts +7 -0
- package/dist/server/routes/admin-active.js +120 -0
- package/dist/server/routes/admin-stats.d.ts +7 -0
- package/dist/server/routes/admin-stats.js +176 -0
- package/dist/server/routes/agent.d.ts +24 -0
- package/dist/server/routes/agent.js +480 -0
- package/dist/server/routes/answer.d.ts +5 -0
- package/dist/server/routes/answer.js +125 -0
- package/dist/server/routes/ask.d.ts +28 -0
- package/dist/server/routes/ask.js +295 -0
- package/dist/server/routes/batch.d.ts +6 -0
- package/dist/server/routes/batch.js +493 -0
- package/dist/server/routes/cache-warm.d.ts +25 -0
- package/dist/server/routes/cache-warm.js +212 -0
- package/dist/server/routes/cli-usage.d.ts +6 -0
- package/dist/server/routes/cli-usage.js +127 -0
- package/dist/server/routes/compat.d.ts +23 -0
- package/dist/server/routes/compat.js +652 -0
- package/dist/server/routes/crawl.d.ts +13 -0
- package/dist/server/routes/crawl.js +287 -0
- package/dist/server/routes/deep-fetch.d.ts +8 -0
- package/dist/server/routes/deep-fetch.js +57 -0
- package/dist/server/routes/deep-research.d.ts +11 -0
- package/dist/server/routes/deep-research.js +232 -0
- package/dist/server/routes/demo.d.ts +24 -0
- package/dist/server/routes/demo.js +517 -0
- package/dist/server/routes/do.d.ts +8 -0
- package/dist/server/routes/do.js +72 -0
- package/dist/server/routes/extract.d.ts +14 -0
- package/dist/server/routes/extract.js +325 -0
- package/dist/server/routes/feed.d.ts +15 -0
- package/dist/server/routes/feed.js +311 -0
- package/dist/server/routes/fetch-queue.d.ts +13 -0
- package/dist/server/routes/fetch-queue.js +357 -0
- package/dist/server/routes/fetch.d.ts +7 -0
- package/dist/server/routes/fetch.js +1274 -0
- package/dist/server/routes/go.d.ts +14 -0
- package/dist/server/routes/go.js +81 -0
- package/dist/server/routes/health.d.ts +11 -0
- package/dist/server/routes/health.js +141 -0
- package/dist/server/routes/jobs.d.ts +7 -0
- package/dist/server/routes/jobs.js +574 -0
- package/dist/server/routes/map.d.ts +11 -0
- package/dist/server/routes/map.js +116 -0
- package/dist/server/routes/mcp.d.ts +14 -0
- package/dist/server/routes/mcp.js +197 -0
- package/dist/server/routes/metrics.d.ts +37 -0
- package/dist/server/routes/metrics.js +149 -0
- package/dist/server/routes/oauth.d.ts +9 -0
- package/dist/server/routes/oauth.js +396 -0
- package/dist/server/routes/playground.d.ts +17 -0
- package/dist/server/routes/playground.js +283 -0
- package/dist/server/routes/reader.d.ts +18 -0
- package/dist/server/routes/reader.js +192 -0
- package/dist/server/routes/research.d.ts +14 -0
- package/dist/server/routes/research.js +482 -0
- package/dist/server/routes/screenshot.d.ts +22 -0
- package/dist/server/routes/screenshot.js +820 -0
- package/dist/server/routes/search.d.ts +6 -0
- package/dist/server/routes/search.js +874 -0
- package/dist/server/routes/session.d.ts +17 -0
- package/dist/server/routes/session.js +548 -0
- package/dist/server/routes/share.d.ts +18 -0
- package/dist/server/routes/share.js +462 -0
- package/dist/server/routes/smart-search/handlers/cars.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/cars.js +102 -0
- package/dist/server/routes/smart-search/handlers/flights.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/flights.js +72 -0
- package/dist/server/routes/smart-search/handlers/general.d.ts +13 -0
- package/dist/server/routes/smart-search/handlers/general.js +717 -0
- package/dist/server/routes/smart-search/handlers/hotels.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/hotels.js +88 -0
- package/dist/server/routes/smart-search/handlers/products.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/products.js +1309 -0
- package/dist/server/routes/smart-search/handlers/rental.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/rental.js +154 -0
- package/dist/server/routes/smart-search/handlers/restaurants.d.ts +2 -0
- package/dist/server/routes/smart-search/handlers/restaurants.js +225 -0
- package/dist/server/routes/smart-search/handlers/transit-verdict.d.ts +41 -0
- package/dist/server/routes/smart-search/handlers/transit-verdict.js +224 -0
- package/dist/server/routes/smart-search/index.d.ts +19 -0
- package/dist/server/routes/smart-search/index.js +546 -0
- package/dist/server/routes/smart-search/intent.d.ts +3 -0
- package/dist/server/routes/smart-search/intent.js +264 -0
- package/dist/server/routes/smart-search/llm.d.ts +16 -0
- package/dist/server/routes/smart-search/llm.js +70 -0
- package/dist/server/routes/smart-search/sources/reddit.d.ts +18 -0
- package/dist/server/routes/smart-search/sources/reddit.js +34 -0
- package/dist/server/routes/smart-search/sources/yelp.d.ts +25 -0
- package/dist/server/routes/smart-search/sources/yelp.js +171 -0
- package/dist/server/routes/smart-search/sources/youtube.d.ts +8 -0
- package/dist/server/routes/smart-search/sources/youtube.js +9 -0
- package/dist/server/routes/smart-search/types.d.ts +81 -0
- package/dist/server/routes/smart-search/types.js +1 -0
- package/dist/server/routes/smart-search/utils.d.ts +20 -0
- package/dist/server/routes/smart-search/utils.js +146 -0
- package/dist/server/routes/stats.d.ts +6 -0
- package/dist/server/routes/stats.js +71 -0
- package/dist/server/routes/stripe.d.ts +15 -0
- package/dist/server/routes/stripe.js +296 -0
- package/dist/server/routes/transcript-export.d.ts +10 -0
- package/dist/server/routes/transcript-export.js +178 -0
- package/dist/server/routes/usage.d.ts +9 -0
- package/dist/server/routes/usage.js +279 -0
- package/dist/server/routes/users.d.ts +8 -0
- package/dist/server/routes/users.js +1867 -0
- package/dist/server/routes/watch.d.ts +15 -0
- package/dist/server/routes/watch.js +309 -0
- package/dist/server/routes/webhooks.d.ts +26 -0
- package/dist/server/routes/webhooks.js +170 -0
- package/dist/server/routes/youtube.d.ts +6 -0
- package/dist/server/routes/youtube.js +130 -0
- package/dist/server/sentry.d.ts +14 -0
- package/dist/server/sentry.js +104 -0
- package/dist/server/types.d.ts +15 -0
- package/dist/server/types.js +7 -0
- package/dist/server/utils/response.d.ts +44 -0
- package/dist/server/utils/response.js +69 -0
- package/dist/server/utils/sse.d.ts +22 -0
- package/dist/server/utils/sse.js +38 -0
- package/dist/types.d.ts +552 -0
- package/dist/types.js +39 -0
- package/llms.txt +105 -0
- package/package.json +189 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth store abstraction for API key validation and usage tracking
|
|
3
|
+
* Designed to easily swap from in-memory to PostgreSQL
|
|
4
|
+
*/
|
|
5
|
+
import { timingSafeEqual } from 'crypto';
|
|
6
|
+
/**
|
|
7
|
+
* Validate API key format and strength
|
|
8
|
+
* SECURITY: Enforce minimum complexity
|
|
9
|
+
*/
|
|
10
|
+
function validateKeyFormat(key) {
|
|
11
|
+
// Minimum 32 characters
|
|
12
|
+
if (key.length < 32) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
// Must contain alphanumeric characters
|
|
16
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(key)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Timing-safe key comparison
|
|
23
|
+
* SECURITY: Prevent timing attacks on key validation
|
|
24
|
+
*/
|
|
25
|
+
function timingSafeKeyCompare(a, b) {
|
|
26
|
+
// Ensure equal length for comparison
|
|
27
|
+
if (a.length !== b.length) {
|
|
28
|
+
// Compare against dummy to prevent timing leak
|
|
29
|
+
const dummy = 'x'.repeat(Math.max(a.length, b.length));
|
|
30
|
+
timingSafeEqual(Buffer.from(dummy), Buffer.from(dummy));
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* In-memory auth store for development and self-hosted deployments
|
|
42
|
+
*/
|
|
43
|
+
export class InMemoryAuthStore {
|
|
44
|
+
keys = new Map();
|
|
45
|
+
usage = new Map();
|
|
46
|
+
constructor() {
|
|
47
|
+
// SECURITY: Demo key only in development mode
|
|
48
|
+
// Removed hardcoded demo key - use addKey() or environment variables
|
|
49
|
+
if (process.env.NODE_ENV === 'development' && process.env.DEMO_KEY) {
|
|
50
|
+
this.addKey({
|
|
51
|
+
key: process.env.DEMO_KEY,
|
|
52
|
+
tier: 'pro',
|
|
53
|
+
rateLimit: 300,
|
|
54
|
+
createdAt: new Date(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async validateKey(key) {
|
|
59
|
+
// Basic validation
|
|
60
|
+
if (!key || typeof key !== 'string') {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// SECURITY: Timing-safe comparison to prevent timing attacks
|
|
64
|
+
for (const [storedKey, keyInfo] of this.keys.entries()) {
|
|
65
|
+
if (timingSafeKeyCompare(key, storedKey)) {
|
|
66
|
+
return keyInfo;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Constant-time operation for invalid key
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
async trackUsage(key, creditsOrType) {
|
|
73
|
+
// For in-memory store, just count everything as 1 credit
|
|
74
|
+
const credits = typeof creditsOrType === 'number' ? creditsOrType : 1;
|
|
75
|
+
const current = this.usage.get(key) || 0;
|
|
76
|
+
this.usage.set(key, current + credits);
|
|
77
|
+
}
|
|
78
|
+
addKey(keyInfo) {
|
|
79
|
+
// SECURITY: Validate key format before adding
|
|
80
|
+
if (!validateKeyFormat(keyInfo.key)) {
|
|
81
|
+
throw new Error('Invalid API key format: must be at least 32 characters, alphanumeric with - or _');
|
|
82
|
+
}
|
|
83
|
+
this.keys.set(keyInfo.key, keyInfo);
|
|
84
|
+
}
|
|
85
|
+
getUsage(key) {
|
|
86
|
+
return this.usage.get(key) || 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bull queue setup for WebPeel microservices.
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - API container (API_MODE=queue) — to enqueue fetch/render jobs
|
|
6
|
+
* - Worker container (WORKER_MODE=1) — to process jobs and write results
|
|
7
|
+
*
|
|
8
|
+
* Queue names:
|
|
9
|
+
* - "webpeel:fetch" — HTTP-only fetches (no browser)
|
|
10
|
+
* - "webpeel:render" — Browser/Playwright fetches (render=true)
|
|
11
|
+
*
|
|
12
|
+
* Job result format stored in Redis:
|
|
13
|
+
* key: webpeel:result:<jobId>
|
|
14
|
+
* value: JSON string of { status, result?, error? }
|
|
15
|
+
* TTL: 24h
|
|
16
|
+
*
|
|
17
|
+
* No secrets in code. All config via env vars:
|
|
18
|
+
* REDIS_URL — e.g. redis://redis:6379 (default)
|
|
19
|
+
* REDIS_PASSWORD — optional password
|
|
20
|
+
*/
|
|
21
|
+
import Bull from 'bull';
|
|
22
|
+
export interface FetchJobPayload {
|
|
23
|
+
jobId: string;
|
|
24
|
+
url: string;
|
|
25
|
+
format?: 'markdown' | 'text' | 'html' | 'clean';
|
|
26
|
+
render?: boolean;
|
|
27
|
+
wait?: number;
|
|
28
|
+
maxTokens?: number;
|
|
29
|
+
budget?: number;
|
|
30
|
+
stealth?: boolean;
|
|
31
|
+
screenshot?: boolean;
|
|
32
|
+
fullPage?: boolean;
|
|
33
|
+
selector?: string;
|
|
34
|
+
exclude?: string[];
|
|
35
|
+
includeTags?: string[];
|
|
36
|
+
excludeTags?: string[];
|
|
37
|
+
images?: boolean;
|
|
38
|
+
actions?: any[];
|
|
39
|
+
timeout?: number;
|
|
40
|
+
lite?: boolean;
|
|
41
|
+
raw?: boolean;
|
|
42
|
+
noDomainApi?: boolean;
|
|
43
|
+
readable?: boolean;
|
|
44
|
+
question?: string;
|
|
45
|
+
highlightQuery?: string;
|
|
46
|
+
highlightMaxChars?: number;
|
|
47
|
+
userId?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function getFetchQueue(): Bull.Queue<FetchJobPayload>;
|
|
50
|
+
export declare function getRenderQueue(): Bull.Queue<FetchJobPayload>;
|
|
51
|
+
export declare const RESULT_KEY_PREFIX = "webpeel:result:";
|
|
52
|
+
export declare const RESULT_TTL_SECONDS = 86400;
|
|
53
|
+
export interface JobResult {
|
|
54
|
+
status: 'queued' | 'processing' | 'completed' | 'failed';
|
|
55
|
+
result?: any;
|
|
56
|
+
error?: string;
|
|
57
|
+
startedAt?: string;
|
|
58
|
+
completedAt?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function closeQueues(): Promise<void>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bull queue setup for WebPeel microservices.
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - API container (API_MODE=queue) — to enqueue fetch/render jobs
|
|
6
|
+
* - Worker container (WORKER_MODE=1) — to process jobs and write results
|
|
7
|
+
*
|
|
8
|
+
* Queue names:
|
|
9
|
+
* - "webpeel:fetch" — HTTP-only fetches (no browser)
|
|
10
|
+
* - "webpeel:render" — Browser/Playwright fetches (render=true)
|
|
11
|
+
*
|
|
12
|
+
* Job result format stored in Redis:
|
|
13
|
+
* key: webpeel:result:<jobId>
|
|
14
|
+
* value: JSON string of { status, result?, error? }
|
|
15
|
+
* TTL: 24h
|
|
16
|
+
*
|
|
17
|
+
* No secrets in code. All config via env vars:
|
|
18
|
+
* REDIS_URL — e.g. redis://redis:6379 (default)
|
|
19
|
+
* REDIS_PASSWORD — optional password
|
|
20
|
+
*/
|
|
21
|
+
import Bull from 'bull';
|
|
22
|
+
// ─── Redis connection config ─────────────────────────────────────────────────
|
|
23
|
+
function getRedisConfig() {
|
|
24
|
+
const url = process.env.REDIS_URL || 'redis://redis:6379';
|
|
25
|
+
const password = process.env.REDIS_PASSWORD || undefined;
|
|
26
|
+
// Parse the URL to extract host/port (Bull accepts host+port or full URL)
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(url);
|
|
29
|
+
return {
|
|
30
|
+
host: parsed.hostname,
|
|
31
|
+
port: parseInt(parsed.port || '6379', 10),
|
|
32
|
+
password,
|
|
33
|
+
db: parseInt(parsed.pathname?.slice(1) || '0', 10) || 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Fallback defaults
|
|
38
|
+
return {
|
|
39
|
+
host: 'redis',
|
|
40
|
+
port: 6379,
|
|
41
|
+
password,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const sharedOpts = {
|
|
46
|
+
redis: getRedisConfig(),
|
|
47
|
+
defaultJobOptions: {
|
|
48
|
+
attempts: 3,
|
|
49
|
+
backoff: {
|
|
50
|
+
type: 'exponential',
|
|
51
|
+
delay: 2000,
|
|
52
|
+
},
|
|
53
|
+
removeOnComplete: { age: 86400, count: 1000 }, // Keep 24h or 1000 jobs, whichever hits first
|
|
54
|
+
removeOnFail: { age: 259200 }, // Keep failed jobs 72h for debugging
|
|
55
|
+
timeout: 120_000, // 2 min hard timeout per job
|
|
56
|
+
},
|
|
57
|
+
// Lock will be extended by workers every 30s — initial lock should be generous
|
|
58
|
+
settings: {
|
|
59
|
+
lockDuration: 60_000, // 60s initial lock (default: 30s)
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
// ─── Queue singletons ────────────────────────────────────────────────────────
|
|
63
|
+
let _fetchQueue = null;
|
|
64
|
+
let _renderQueue = null;
|
|
65
|
+
export function getFetchQueue() {
|
|
66
|
+
if (!_fetchQueue) {
|
|
67
|
+
_fetchQueue = new Bull('webpeel:fetch', sharedOpts);
|
|
68
|
+
}
|
|
69
|
+
return _fetchQueue;
|
|
70
|
+
}
|
|
71
|
+
export function getRenderQueue() {
|
|
72
|
+
if (!_renderQueue) {
|
|
73
|
+
_renderQueue = new Bull('webpeel:render', sharedOpts);
|
|
74
|
+
}
|
|
75
|
+
return _renderQueue;
|
|
76
|
+
}
|
|
77
|
+
// ─── Result helpers (Redis key = webpeel:result:<jobId>) ─────────────────────
|
|
78
|
+
export const RESULT_KEY_PREFIX = 'webpeel:result:';
|
|
79
|
+
export const RESULT_TTL_SECONDS = 86_400; // 24 hours
|
|
80
|
+
// ─── Graceful teardown ───────────────────────────────────────────────────────
|
|
81
|
+
export async function closeQueues() {
|
|
82
|
+
const closes = [];
|
|
83
|
+
if (_fetchQueue)
|
|
84
|
+
closes.push(_fetchQueue.close());
|
|
85
|
+
if (_renderQueue)
|
|
86
|
+
closes.push(_renderQueue.close());
|
|
87
|
+
await Promise.all(closes);
|
|
88
|
+
_fetchQueue = null;
|
|
89
|
+
_renderQueue = null;
|
|
90
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email service using Nodemailer (free, no API key required).
|
|
3
|
+
* Configure via environment variables:
|
|
4
|
+
* EMAIL_SMTP_HOST - SMTP server (e.g. smtp.gmail.com)
|
|
5
|
+
* EMAIL_SMTP_PORT - SMTP port (default: 587)
|
|
6
|
+
* EMAIL_SMTP_USER - SMTP username / email address
|
|
7
|
+
* EMAIL_SMTP_PASS - SMTP password (Gmail: use App Password)
|
|
8
|
+
* EMAIL_FROM - From address (default: EMAIL_SMTP_USER)
|
|
9
|
+
*
|
|
10
|
+
* Gmail setup: https://myaccount.google.com/apppasswords
|
|
11
|
+
* Use "App Password" (not your regular password).
|
|
12
|
+
*/
|
|
13
|
+
import type { Pool as PgPool } from 'pg';
|
|
14
|
+
export interface UsageAlertEmailParams {
|
|
15
|
+
toEmail: string;
|
|
16
|
+
userName?: string;
|
|
17
|
+
usagePercent: number;
|
|
18
|
+
used: number;
|
|
19
|
+
total: number;
|
|
20
|
+
tier: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function sendUsageAlertEmail(params: UsageAlertEmailParams): Promise<boolean>;
|
|
23
|
+
export interface UsageAlertCheckResult {
|
|
24
|
+
/**
|
|
25
|
+
* Which threshold was crossed (80 | 90), or null if no alert to send.
|
|
26
|
+
* Priority: 90 > 80 (only one alert per call).
|
|
27
|
+
*/
|
|
28
|
+
threshold: 80 | 90 | null;
|
|
29
|
+
usagePercent: number;
|
|
30
|
+
used: number;
|
|
31
|
+
total: number;
|
|
32
|
+
userEmail: string;
|
|
33
|
+
userName?: string;
|
|
34
|
+
userTier: string;
|
|
35
|
+
/** Custom alert email if set, otherwise falls back to userEmail */
|
|
36
|
+
alertEmail: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check whether a usage alert should be sent for a given user and,
|
|
40
|
+
* if so, return the alert details plus automatically update the
|
|
41
|
+
* `alert_sent_80_at` / `alert_sent_90_at` column.
|
|
42
|
+
*
|
|
43
|
+
* Thresholds are **automatic** (80% and 90%) and work independently of
|
|
44
|
+
* the user-configured `alert_threshold` system.
|
|
45
|
+
*
|
|
46
|
+
* Call this fire-and-forget style after each successful API request:
|
|
47
|
+
* ```ts
|
|
48
|
+
* checkAndSendDualAlert(pool, userId).catch(() => {});
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function checkAndSendDualAlert(pool: PgPool, userId: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Send password reset email with a secure reset link.
|
|
54
|
+
*/
|
|
55
|
+
export declare function sendPasswordResetEmail(toEmail: string, resetUrl: string): Promise<boolean>;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email service using Nodemailer (free, no API key required).
|
|
3
|
+
* Configure via environment variables:
|
|
4
|
+
* EMAIL_SMTP_HOST - SMTP server (e.g. smtp.gmail.com)
|
|
5
|
+
* EMAIL_SMTP_PORT - SMTP port (default: 587)
|
|
6
|
+
* EMAIL_SMTP_USER - SMTP username / email address
|
|
7
|
+
* EMAIL_SMTP_PASS - SMTP password (Gmail: use App Password)
|
|
8
|
+
* EMAIL_FROM - From address (default: EMAIL_SMTP_USER)
|
|
9
|
+
*
|
|
10
|
+
* Gmail setup: https://myaccount.google.com/apppasswords
|
|
11
|
+
* Use "App Password" (not your regular password).
|
|
12
|
+
*/
|
|
13
|
+
import nodemailer from 'nodemailer';
|
|
14
|
+
import { Resend } from 'resend';
|
|
15
|
+
// ── Resend (primary — sends from noreply@webpeel.dev) ─────────────────────
|
|
16
|
+
function getResend() {
|
|
17
|
+
const key = process.env.RESEND_API_KEY;
|
|
18
|
+
if (!key)
|
|
19
|
+
return null;
|
|
20
|
+
return new Resend(key);
|
|
21
|
+
}
|
|
22
|
+
// ── Nodemailer (fallback — Gmail SMTP) ────────────────────────────────────
|
|
23
|
+
function createTransport() {
|
|
24
|
+
const host = process.env.EMAIL_SMTP_HOST;
|
|
25
|
+
const user = process.env.EMAIL_SMTP_USER;
|
|
26
|
+
const pass = process.env.EMAIL_SMTP_PASS;
|
|
27
|
+
if (!host || !user || !pass) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return nodemailer.createTransport({
|
|
31
|
+
host,
|
|
32
|
+
port: parseInt(process.env.EMAIL_SMTP_PORT || '587'),
|
|
33
|
+
secure: process.env.EMAIL_SMTP_PORT === '465',
|
|
34
|
+
auth: { user, pass },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Send an email via Resend (primary) or Nodemailer (fallback).
|
|
39
|
+
* Returns true if sent successfully.
|
|
40
|
+
*/
|
|
41
|
+
async function sendEmail(options) {
|
|
42
|
+
const fromAddress = process.env.EMAIL_FROM || 'noreply@webpeel.dev';
|
|
43
|
+
const fromName = 'WebPeel';
|
|
44
|
+
// Try Resend first (proper From address, no Gmail override)
|
|
45
|
+
const resend = getResend();
|
|
46
|
+
if (resend) {
|
|
47
|
+
try {
|
|
48
|
+
await resend.emails.send({
|
|
49
|
+
from: `${fromName} <${fromAddress}>`,
|
|
50
|
+
to: options.to,
|
|
51
|
+
subject: options.subject,
|
|
52
|
+
html: options.html,
|
|
53
|
+
});
|
|
54
|
+
console.log(`[email] Sent via Resend to: ${options.to}`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.warn('[email] Resend failed, trying Nodemailer fallback:', err.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Fallback to Nodemailer/SMTP
|
|
62
|
+
const transport = createTransport();
|
|
63
|
+
if (transport) {
|
|
64
|
+
try {
|
|
65
|
+
const smtpFrom = process.env.EMAIL_SMTP_USER || fromAddress;
|
|
66
|
+
await transport.sendMail({
|
|
67
|
+
from: `${fromName} <${smtpFrom}>`,
|
|
68
|
+
to: options.to,
|
|
69
|
+
subject: options.subject,
|
|
70
|
+
html: options.html,
|
|
71
|
+
});
|
|
72
|
+
console.log(`[email] Sent via SMTP to: ${options.to}`);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error('[email] SMTP send failed:', err.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.warn('[email] No email provider configured (set RESEND_API_KEY or EMAIL_SMTP_*)');
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
export async function sendUsageAlertEmail(params) {
|
|
83
|
+
const html = buildUsageAlertHtml(params);
|
|
84
|
+
return sendEmail({
|
|
85
|
+
to: params.toEmail,
|
|
86
|
+
subject: `WebPeel: You've used ${params.usagePercent}% of your weekly API limit`,
|
|
87
|
+
html,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function buildUsageAlertHtml(params) {
|
|
91
|
+
const { usagePercent, used, total, tier, userName } = params;
|
|
92
|
+
const color = usagePercent >= 90 ? '#ef4444' : usagePercent >= 75 ? '#f59e0b' : '#5865F2';
|
|
93
|
+
return `<!DOCTYPE html>
|
|
94
|
+
<html>
|
|
95
|
+
<head><meta charset="utf-8"><title>WebPeel Usage Alert</title></head>
|
|
96
|
+
<body style="margin:0;padding:0;background:#0A0A0F;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;">
|
|
97
|
+
<div style="max-width:520px;margin:40px auto;background:#111116;border:1px solid #27272a;border-radius:12px;overflow:hidden;">
|
|
98
|
+
<div style="background:${color};padding:4px 24px;"></div>
|
|
99
|
+
<div style="padding:32px 24px;">
|
|
100
|
+
<div style="font-size:24px;font-weight:700;color:#ffffff;margin-bottom:8px;">Usage Alert</div>
|
|
101
|
+
<div style="font-size:15px;color:#a1a1aa;margin-bottom:24px;">
|
|
102
|
+
${userName ? `Hi ${userName}, you've` : "You've"} used <strong style="color:#ffffff;">${usagePercent}%</strong> of your weekly API limit.
|
|
103
|
+
</div>
|
|
104
|
+
<div style="background:#18181b;border-radius:8px;padding:16px;margin-bottom:24px;">
|
|
105
|
+
<div style="font-size:13px;color:#a1a1aa;margin-bottom:8px;">Usage this week</div>
|
|
106
|
+
<div style="height:8px;background:#27272a;border-radius:4px;overflow:hidden;">
|
|
107
|
+
<div style="height:100%;width:${Math.min(usagePercent, 100)}%;background:${color};border-radius:4px;"></div>
|
|
108
|
+
</div>
|
|
109
|
+
<div style="font-size:13px;color:#a1a1aa;margin-top:8px;">${used} / ${total} requests · ${tier} plan</div>
|
|
110
|
+
</div>
|
|
111
|
+
<a href="https://app.webpeel.dev/billing" style="display:inline-block;background:#5865F2;color:#ffffff;padding:12px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:14px;">Upgrade Plan →</a>
|
|
112
|
+
<div style="font-size:12px;color:#71717a;margin-top:24px;">
|
|
113
|
+
To disable these alerts, visit <a href="https://app.webpeel.dev/settings" style="color:#5865F2;">Settings</a>.
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</body>
|
|
118
|
+
</html>`;
|
|
119
|
+
}
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Dual-threshold automatic alert system (80% and 90%)
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
/** Week string in "YYYY-Www" format, consistent with pg-auth-store */
|
|
124
|
+
function getCurrentWeek() {
|
|
125
|
+
const now = new Date();
|
|
126
|
+
const year = now.getUTCFullYear();
|
|
127
|
+
const jan4 = new Date(Date.UTC(year, 0, 4));
|
|
128
|
+
const weekNum = Math.ceil(((now.getTime() - jan4.getTime()) / 86400000 + jan4.getUTCDay() + 1) / 7);
|
|
129
|
+
return `${year}-W${String(weekNum).padStart(2, '0')}`;
|
|
130
|
+
}
|
|
131
|
+
/** Returns true if the timestamp is within the current ISO week */
|
|
132
|
+
function isSentThisWeek(ts) {
|
|
133
|
+
if (!ts)
|
|
134
|
+
return false;
|
|
135
|
+
const now = new Date();
|
|
136
|
+
// Start of current week (Monday 00:00 UTC)
|
|
137
|
+
const dayOfWeek = now.getUTCDay(); // 0=Sun, 1=Mon...
|
|
138
|
+
const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
139
|
+
const weekStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - daysToMonday));
|
|
140
|
+
return ts >= weekStart;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check whether a usage alert should be sent for a given user and,
|
|
144
|
+
* if so, return the alert details plus automatically update the
|
|
145
|
+
* `alert_sent_80_at` / `alert_sent_90_at` column.
|
|
146
|
+
*
|
|
147
|
+
* Thresholds are **automatic** (80% and 90%) and work independently of
|
|
148
|
+
* the user-configured `alert_threshold` system.
|
|
149
|
+
*
|
|
150
|
+
* Call this fire-and-forget style after each successful API request:
|
|
151
|
+
* ```ts
|
|
152
|
+
* checkAndSendDualAlert(pool, userId).catch(() => {});
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export async function checkAndSendDualAlert(pool, userId) {
|
|
156
|
+
try {
|
|
157
|
+
const currentWeek = getCurrentWeek();
|
|
158
|
+
const result = await pool.query(`SELECT u.email, u.name, u.tier, u.alert_email,
|
|
159
|
+
u.alert_sent_80_at, u.alert_sent_90_at,
|
|
160
|
+
u.weekly_limit,
|
|
161
|
+
COALESCE(SUM(wu.total_count), 0) AS total_used,
|
|
162
|
+
u.weekly_limit + COALESCE(MAX(wu.rollover_credits), 0) AS total_available
|
|
163
|
+
FROM users u
|
|
164
|
+
LEFT JOIN api_keys ak ON ak.user_id = u.id
|
|
165
|
+
LEFT JOIN weekly_usage wu ON wu.api_key_id = ak.id AND wu.week = $2
|
|
166
|
+
WHERE u.id = $1
|
|
167
|
+
GROUP BY u.id, u.email, u.name, u.tier, u.alert_email,
|
|
168
|
+
u.alert_sent_80_at, u.alert_sent_90_at, u.weekly_limit`, [userId, currentWeek]);
|
|
169
|
+
const row = result.rows[0];
|
|
170
|
+
if (!row)
|
|
171
|
+
return;
|
|
172
|
+
const used = parseInt(row.total_used, 10) || 0;
|
|
173
|
+
const total = parseInt(row.total_available, 10) || row.weekly_limit || 999;
|
|
174
|
+
const usagePercent = total > 0 ? Math.round((used / total) * 100) : 0;
|
|
175
|
+
const alertEmail = row.alert_email || row.email;
|
|
176
|
+
const sharedParams = {
|
|
177
|
+
toEmail: alertEmail,
|
|
178
|
+
userName: row.name || undefined,
|
|
179
|
+
used,
|
|
180
|
+
total,
|
|
181
|
+
tier: row.tier,
|
|
182
|
+
};
|
|
183
|
+
// Check 90% threshold first (higher priority)
|
|
184
|
+
if (usagePercent >= 90 && !isSentThisWeek(row.alert_sent_90_at ? new Date(row.alert_sent_90_at) : null)) {
|
|
185
|
+
const sent = await sendUsageAlertEmail({ ...sharedParams, usagePercent: 90 });
|
|
186
|
+
if (sent) {
|
|
187
|
+
await pool.query('UPDATE users SET alert_sent_90_at = NOW() WHERE id = $1', [userId]);
|
|
188
|
+
console.log(`[alert] Sent 90% usage alert to ${alertEmail} (user ${userId})`);
|
|
189
|
+
}
|
|
190
|
+
return; // Only one alert per call
|
|
191
|
+
}
|
|
192
|
+
// Check 80% threshold (lower priority — don't send if already sent 90%)
|
|
193
|
+
if (usagePercent >= 80 && !isSentThisWeek(row.alert_sent_80_at ? new Date(row.alert_sent_80_at) : null)) {
|
|
194
|
+
const sent = await sendUsageAlertEmail({ ...sharedParams, usagePercent: 80 });
|
|
195
|
+
if (sent) {
|
|
196
|
+
await pool.query('UPDATE users SET alert_sent_80_at = NOW() WHERE id = $1', [userId]);
|
|
197
|
+
console.log(`[alert] Sent 80% usage alert to ${alertEmail} (user ${userId})`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
// Never let alert errors surface to callers
|
|
203
|
+
console.warn('[alert] checkAndSendDualAlert failed:', err);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Send password reset email with a secure reset link.
|
|
208
|
+
*/
|
|
209
|
+
export async function sendPasswordResetEmail(toEmail, resetUrl) {
|
|
210
|
+
const result = await sendEmail({
|
|
211
|
+
to: toEmail,
|
|
212
|
+
subject: 'Reset your WebPeel password',
|
|
213
|
+
html: `<!DOCTYPE html>
|
|
214
|
+
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
|
|
215
|
+
<body style="margin:0;padding:0;background-color:#f6f6f9;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;">
|
|
216
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f6f6f9;padding:40px 0;">
|
|
217
|
+
<tr><td align="center">
|
|
218
|
+
<table role="presentation" width="480" cellpadding="0" cellspacing="0" style="max-width:480px;width:100%;">
|
|
219
|
+
|
|
220
|
+
<!-- Logo -->
|
|
221
|
+
<tr><td align="center" style="padding:0 0 32px;">
|
|
222
|
+
<table role="presentation" cellpadding="0" cellspacing="0"><tr>
|
|
223
|
+
<td style="background-color:#5865F2;border-radius:10px;width:40px;height:40px;text-align:center;vertical-align:middle;">
|
|
224
|
+
<span style="color:#ffffff;font-size:20px;font-weight:700;line-height:40px;">W</span>
|
|
225
|
+
</td>
|
|
226
|
+
<td style="padding-left:12px;">
|
|
227
|
+
<span style="font-size:22px;font-weight:700;color:#1a1a2e;letter-spacing:-0.5px;">WebPeel</span>
|
|
228
|
+
</td>
|
|
229
|
+
</tr></table>
|
|
230
|
+
</td></tr>
|
|
231
|
+
|
|
232
|
+
<!-- Card -->
|
|
233
|
+
<tr><td>
|
|
234
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border-radius:16px;border:1px solid #e5e5ea;overflow:hidden;">
|
|
235
|
+
|
|
236
|
+
<!-- Purple accent bar -->
|
|
237
|
+
<tr><td style="background-color:#5865F2;height:4px;font-size:0;line-height:0;"> </td></tr>
|
|
238
|
+
|
|
239
|
+
<!-- Content -->
|
|
240
|
+
<tr><td style="padding:40px 36px;">
|
|
241
|
+
<h1 style="margin:0 0 16px;font-size:22px;font-weight:700;color:#1a1a2e;">Reset your password</h1>
|
|
242
|
+
<p style="margin:0 0 8px;font-size:15px;line-height:1.6;color:#4a4a5a;">Hi there,</p>
|
|
243
|
+
<p style="margin:0 0 28px;font-size:15px;line-height:1.6;color:#4a4a5a;">
|
|
244
|
+
We received a request to reset your WebPeel account password. Click the button below to choose a new one. This link expires in <strong>1 hour</strong>.
|
|
245
|
+
</p>
|
|
246
|
+
|
|
247
|
+
<!-- Button -->
|
|
248
|
+
<table role="presentation" cellpadding="0" cellspacing="0" style="margin:0 auto 28px;">
|
|
249
|
+
<tr><td align="center" style="background-color:#5865F2;border-radius:10px;">
|
|
250
|
+
<a href="${resetUrl}" target="_blank" style="display:inline-block;padding:14px 36px;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;letter-spacing:0.3px;">
|
|
251
|
+
Reset Password
|
|
252
|
+
</a>
|
|
253
|
+
</td></tr>
|
|
254
|
+
</table>
|
|
255
|
+
|
|
256
|
+
<!-- Fallback URL -->
|
|
257
|
+
<p style="margin:0 0 24px;font-size:12px;line-height:1.5;color:#9a9aaa;word-break:break-all;">
|
|
258
|
+
Or copy this link: <a href="${resetUrl}" style="color:#5865F2;">${resetUrl}</a>
|
|
259
|
+
</p>
|
|
260
|
+
|
|
261
|
+
<hr style="border:none;border-top:1px solid #eeeef2;margin:0 0 20px;">
|
|
262
|
+
<p style="margin:0;font-size:13px;line-height:1.5;color:#9a9aaa;">
|
|
263
|
+
If you didn't request this, no action is needed — your password won't change.
|
|
264
|
+
</p>
|
|
265
|
+
</td></tr>
|
|
266
|
+
</table>
|
|
267
|
+
</td></tr>
|
|
268
|
+
|
|
269
|
+
<!-- Footer -->
|
|
270
|
+
<tr><td align="center" style="padding:28px 0 0;">
|
|
271
|
+
<p style="margin:0 0 6px;font-size:12px;color:#9a9aaa;">
|
|
272
|
+
© ${new Date().getFullYear()} WebPeel — The web data platform for AI
|
|
273
|
+
</p>
|
|
274
|
+
<p style="margin:0;font-size:12px;">
|
|
275
|
+
<a href="https://webpeel.dev" style="color:#5865F2;text-decoration:none;">webpeel.dev</a>
|
|
276
|
+
·
|
|
277
|
+
<a href="https://app.webpeel.dev" style="color:#5865F2;text-decoration:none;">Dashboard</a>
|
|
278
|
+
</p>
|
|
279
|
+
</td></tr>
|
|
280
|
+
|
|
281
|
+
</table>
|
|
282
|
+
</td></tr>
|
|
283
|
+
</table>
|
|
284
|
+
</body></html>`,
|
|
285
|
+
});
|
|
286
|
+
if (!result) {
|
|
287
|
+
console.warn('[email] Password reset email not sent to:', toEmail);
|
|
288
|
+
console.warn('[email] Reset URL:', resetUrl);
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Job queue for async operations
|
|
3
|
+
*
|
|
4
|
+
* Factory creates PostgreSQL-backed queue in production or in-memory queue for local dev.
|
|
5
|
+
* Tracks crawl, batch scrape, and extraction jobs with progress updates.
|
|
6
|
+
*/
|
|
7
|
+
export interface WebhookConfig {
|
|
8
|
+
url: string;
|
|
9
|
+
events: ('started' | 'page' | 'completed' | 'failed')[];
|
|
10
|
+
metadata?: Record<string, any>;
|
|
11
|
+
secret?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface WebhookDeliveryResult {
|
|
14
|
+
url: string;
|
|
15
|
+
delivered: boolean;
|
|
16
|
+
deliveredAt?: string;
|
|
17
|
+
statusCode?: number;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface Job {
|
|
21
|
+
id: string;
|
|
22
|
+
type: 'crawl' | 'batch' | 'extract';
|
|
23
|
+
status: 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
|
24
|
+
progress: number;
|
|
25
|
+
total: number;
|
|
26
|
+
completed: number;
|
|
27
|
+
creditsUsed: number;
|
|
28
|
+
data: any[];
|
|
29
|
+
error?: string;
|
|
30
|
+
webhook?: WebhookConfig;
|
|
31
|
+
webhookDelivery?: WebhookDeliveryResult;
|
|
32
|
+
ownerId?: string;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
expiresAt: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Job queue interface - implemented by both in-memory and PostgreSQL queues
|
|
39
|
+
*/
|
|
40
|
+
export interface IJobQueue {
|
|
41
|
+
createJob(type: Job['type'], webhook?: WebhookConfig, ownerId?: string): Job | Promise<Job>;
|
|
42
|
+
getJob(id: string): Job | null | Promise<Job | null>;
|
|
43
|
+
updateJob(id: string, update: Partial<Job>): void | Promise<void>;
|
|
44
|
+
cancelJob(id: string): boolean | Promise<boolean>;
|
|
45
|
+
listJobs(options?: {
|
|
46
|
+
type?: string;
|
|
47
|
+
status?: string;
|
|
48
|
+
limit?: number;
|
|
49
|
+
ownerId?: string;
|
|
50
|
+
}): Job[] | Promise<Job[]>;
|
|
51
|
+
destroy(): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* In-memory job queue for local development
|
|
55
|
+
*/
|
|
56
|
+
export declare class InMemoryJobQueue implements IJobQueue {
|
|
57
|
+
private jobs;
|
|
58
|
+
private cleanupInterval;
|
|
59
|
+
constructor();
|
|
60
|
+
/**
|
|
61
|
+
* Create a new job
|
|
62
|
+
*/
|
|
63
|
+
createJob(type: Job['type'], webhook?: WebhookConfig, ownerId?: string): Job;
|
|
64
|
+
/**
|
|
65
|
+
* Get a job by ID
|
|
66
|
+
*/
|
|
67
|
+
getJob(id: string): Job | null;
|
|
68
|
+
/**
|
|
69
|
+
* Update a job
|
|
70
|
+
*/
|
|
71
|
+
updateJob(id: string, update: Partial<Job>): void;
|
|
72
|
+
/**
|
|
73
|
+
* Cancel a job
|
|
74
|
+
*/
|
|
75
|
+
cancelJob(id: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* List jobs with optional filters
|
|
78
|
+
*/
|
|
79
|
+
listJobs(options?: {
|
|
80
|
+
type?: string;
|
|
81
|
+
status?: string;
|
|
82
|
+
limit?: number;
|
|
83
|
+
ownerId?: string;
|
|
84
|
+
}): Job[];
|
|
85
|
+
/**
|
|
86
|
+
* Remove expired jobs
|
|
87
|
+
*/
|
|
88
|
+
cleanExpired(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Clean up interval on shutdown
|
|
91
|
+
*/
|
|
92
|
+
destroy(): void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create job queue based on environment
|
|
96
|
+
* - Uses PostgreSQL if DATABASE_URL is set
|
|
97
|
+
* - Falls back to in-memory for local development
|
|
98
|
+
*/
|
|
99
|
+
export declare function createJobQueue(): IJobQueue;
|
|
100
|
+
export declare const jobQueue: InMemoryJobQueue;
|