@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,145 @@
|
|
|
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
|
+
import { randomUUID } from 'crypto';
|
|
8
|
+
import { PostgresJobQueue } from './pg-job-queue.js';
|
|
9
|
+
import { createLogger } from './logger.js';
|
|
10
|
+
const log = createLogger('job-queue');
|
|
11
|
+
/**
|
|
12
|
+
* In-memory job queue for local development
|
|
13
|
+
*/
|
|
14
|
+
export class InMemoryJobQueue {
|
|
15
|
+
jobs = new Map();
|
|
16
|
+
cleanupInterval;
|
|
17
|
+
constructor() {
|
|
18
|
+
// Clean expired jobs every hour
|
|
19
|
+
this.cleanupInterval = setInterval(() => {
|
|
20
|
+
this.cleanExpired();
|
|
21
|
+
}, 60 * 60 * 1000);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a new job
|
|
25
|
+
*/
|
|
26
|
+
createJob(type, webhook, ownerId) {
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const job = {
|
|
29
|
+
id: randomUUID(),
|
|
30
|
+
type,
|
|
31
|
+
status: 'queued',
|
|
32
|
+
progress: 0,
|
|
33
|
+
total: 0,
|
|
34
|
+
completed: 0,
|
|
35
|
+
creditsUsed: 0,
|
|
36
|
+
data: [],
|
|
37
|
+
webhook,
|
|
38
|
+
ownerId,
|
|
39
|
+
createdAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
expiresAt: new Date(Date.now() + 25 * 60 * 60 * 1000).toISOString(), // 25h from now (updated on completion)
|
|
42
|
+
};
|
|
43
|
+
this.jobs.set(job.id, job);
|
|
44
|
+
return job;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get a job by ID
|
|
48
|
+
*/
|
|
49
|
+
getJob(id) {
|
|
50
|
+
return this.jobs.get(id) || null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Update a job
|
|
54
|
+
*/
|
|
55
|
+
updateJob(id, update) {
|
|
56
|
+
const job = this.jobs.get(id);
|
|
57
|
+
if (!job)
|
|
58
|
+
return;
|
|
59
|
+
Object.assign(job, update, {
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
});
|
|
62
|
+
// When job completes/fails, set expiration to 24h from now
|
|
63
|
+
if (update.status === 'completed' || update.status === 'failed') {
|
|
64
|
+
job.expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
65
|
+
}
|
|
66
|
+
// Update progress percentage
|
|
67
|
+
if (job.total > 0) {
|
|
68
|
+
job.progress = Math.round((job.completed / job.total) * 100);
|
|
69
|
+
}
|
|
70
|
+
this.jobs.set(id, job);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Cancel a job
|
|
74
|
+
*/
|
|
75
|
+
cancelJob(id) {
|
|
76
|
+
const job = this.jobs.get(id);
|
|
77
|
+
if (!job)
|
|
78
|
+
return false;
|
|
79
|
+
// Can only cancel queued or processing jobs
|
|
80
|
+
if (job.status !== 'queued' && job.status !== 'processing') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
this.updateJob(id, {
|
|
84
|
+
status: 'cancelled',
|
|
85
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
86
|
+
});
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* List jobs with optional filters
|
|
91
|
+
*/
|
|
92
|
+
listJobs(options) {
|
|
93
|
+
let jobs = Array.from(this.jobs.values());
|
|
94
|
+
if (options?.ownerId) {
|
|
95
|
+
jobs = jobs.filter(j => j.ownerId === options.ownerId);
|
|
96
|
+
}
|
|
97
|
+
if (options?.type) {
|
|
98
|
+
jobs = jobs.filter(j => j.type === options.type);
|
|
99
|
+
}
|
|
100
|
+
if (options?.status) {
|
|
101
|
+
jobs = jobs.filter(j => j.status === options.status);
|
|
102
|
+
}
|
|
103
|
+
// Sort by creation time (newest first)
|
|
104
|
+
jobs.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
105
|
+
if (options?.limit) {
|
|
106
|
+
jobs = jobs.slice(0, options.limit);
|
|
107
|
+
}
|
|
108
|
+
return jobs;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Remove expired jobs
|
|
112
|
+
*/
|
|
113
|
+
cleanExpired() {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
for (const [id, job] of this.jobs.entries()) {
|
|
116
|
+
if (new Date(job.expiresAt).getTime() < now) {
|
|
117
|
+
this.jobs.delete(id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Clean up interval on shutdown
|
|
123
|
+
*/
|
|
124
|
+
destroy() {
|
|
125
|
+
clearInterval(this.cleanupInterval);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Create job queue based on environment
|
|
130
|
+
* - Uses PostgreSQL if DATABASE_URL is set
|
|
131
|
+
* - Falls back to in-memory for local development
|
|
132
|
+
*/
|
|
133
|
+
export function createJobQueue() {
|
|
134
|
+
if (process.env.DATABASE_URL) {
|
|
135
|
+
log.info('Using PostgreSQL job queue');
|
|
136
|
+
return new PostgresJobQueue();
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
log.info('Using in-memory job queue');
|
|
140
|
+
return new InMemoryJobQueue();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Legacy global instance for backwards compatibility (deprecated)
|
|
144
|
+
// @deprecated Use createJobQueue() instead
|
|
145
|
+
export const jobQueue = new InMemoryJobQueue();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight structured logger — no external dependencies.
|
|
3
|
+
* Respects LOG_LEVEL env var: debug | info | warn | error (default: info)
|
|
4
|
+
*/
|
|
5
|
+
export declare function createLogger(component: string): {
|
|
6
|
+
debug(msg: string, data?: Record<string, unknown>): void;
|
|
7
|
+
info(msg: string, data?: Record<string, unknown>): void;
|
|
8
|
+
warn(msg: string, data?: Record<string, unknown>): void;
|
|
9
|
+
error(msg: string, data?: Record<string, unknown>): void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight structured logger — no external dependencies.
|
|
3
|
+
* Respects LOG_LEVEL env var: debug | info | warn | error (default: info)
|
|
4
|
+
*/
|
|
5
|
+
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
6
|
+
const LOG_LEVEL = (process.env.LOG_LEVEL ?? 'info');
|
|
7
|
+
function shouldLog(level) {
|
|
8
|
+
return LEVELS[level] >= (LEVELS[LOG_LEVEL] ?? LEVELS.info);
|
|
9
|
+
}
|
|
10
|
+
function formatLog(level, component, message, data) {
|
|
11
|
+
const ts = new Date().toISOString();
|
|
12
|
+
const base = `${ts} [${level.toUpperCase()}] [${component}] ${message}`;
|
|
13
|
+
if (data && Object.keys(data).length > 0) {
|
|
14
|
+
return `${base} ${JSON.stringify(data)}`;
|
|
15
|
+
}
|
|
16
|
+
return base;
|
|
17
|
+
}
|
|
18
|
+
export function createLogger(component) {
|
|
19
|
+
return {
|
|
20
|
+
debug(msg, data) {
|
|
21
|
+
if (shouldLog('debug'))
|
|
22
|
+
console.debug(formatLog('debug', component, msg, data));
|
|
23
|
+
},
|
|
24
|
+
info(msg, data) {
|
|
25
|
+
if (shouldLog('info'))
|
|
26
|
+
console.info(formatLog('info', component, msg, data));
|
|
27
|
+
},
|
|
28
|
+
warn(msg, data) {
|
|
29
|
+
if (shouldLog('warn'))
|
|
30
|
+
console.warn(formatLog('warn', component, msg, data));
|
|
31
|
+
},
|
|
32
|
+
error(msg, data) {
|
|
33
|
+
if (shouldLog('error'))
|
|
34
|
+
console.error(formatLog('error', component, msg, data));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebPeel Audit Logging Middleware
|
|
3
|
+
*
|
|
4
|
+
* Records who accessed which API endpoints and the outcome.
|
|
5
|
+
* Designed to be privacy-safe:
|
|
6
|
+
* - Logs userId, keyId, method, path, status, duration, IP, user-agent
|
|
7
|
+
* - Does NOT log: request bodies, auth headers, query params (may contain API keys)
|
|
8
|
+
* - Only logs /v1/ endpoints (skips health checks, static files)
|
|
9
|
+
*
|
|
10
|
+
* When DATABASE_URL is set, also writes to usage_logs table (fire-and-forget).
|
|
11
|
+
*/
|
|
12
|
+
import { Request, Response, NextFunction } from 'express';
|
|
13
|
+
import '../types.js';
|
|
14
|
+
export declare function auditMiddleware(req: Request, res: Response, next: NextFunction): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebPeel Audit Logging Middleware
|
|
3
|
+
*
|
|
4
|
+
* Records who accessed which API endpoints and the outcome.
|
|
5
|
+
* Designed to be privacy-safe:
|
|
6
|
+
* - Logs userId, keyId, method, path, status, duration, IP, user-agent
|
|
7
|
+
* - Does NOT log: request bodies, auth headers, query params (may contain API keys)
|
|
8
|
+
* - Only logs /v1/ endpoints (skips health checks, static files)
|
|
9
|
+
*
|
|
10
|
+
* When DATABASE_URL is set, also writes to usage_logs table (fire-and-forget).
|
|
11
|
+
*/
|
|
12
|
+
import '../types.js'; // Augments Express.Request with requestId
|
|
13
|
+
import { createLogger } from '../logger.js';
|
|
14
|
+
import pg from 'pg';
|
|
15
|
+
const auditLog = createLogger('audit');
|
|
16
|
+
// ── Singleton audit DB pool ───────────────────────────────────────────────────
|
|
17
|
+
// One small pool shared by all requests — created lazily on first /v1/ request.
|
|
18
|
+
let _auditPool = null;
|
|
19
|
+
function getAuditPool() {
|
|
20
|
+
if (!_auditPool && process.env.DATABASE_URL) {
|
|
21
|
+
_auditPool = new pg.Pool({
|
|
22
|
+
connectionString: process.env.DATABASE_URL,
|
|
23
|
+
ssl: process.env.DATABASE_URL.includes('sslmode=require')
|
|
24
|
+
? { rejectUnauthorized: process.env.PG_REJECT_UNAUTHORIZED !== 'false' }
|
|
25
|
+
: undefined,
|
|
26
|
+
max: 3,
|
|
27
|
+
idleTimeoutMillis: 30_000,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return _auditPool;
|
|
31
|
+
}
|
|
32
|
+
export function auditMiddleware(req, res, next) {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
res.on('finish', () => {
|
|
35
|
+
const duration = Date.now() - startTime;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
const auth = req.auth;
|
|
38
|
+
const userId = auth?.keyInfo?.accountId || 'anonymous';
|
|
39
|
+
// Use a truncated prefix of the key as a safe identifier (never log the full key)
|
|
40
|
+
const rawKey = auth?.keyInfo?.key;
|
|
41
|
+
const keyId = rawKey ? rawKey.slice(0, 8) + '...' : 'none';
|
|
42
|
+
const clientIp = req.headers['cf-connecting-ip'] ||
|
|
43
|
+
req.headers['x-forwarded-for'] ||
|
|
44
|
+
req.ip;
|
|
45
|
+
// Only log API endpoints (skip health checks, static files)
|
|
46
|
+
if (req.path.startsWith('/v1/')) {
|
|
47
|
+
auditLog.info(`${req.method} ${req.path}`, {
|
|
48
|
+
action: `${req.method} ${req.path}`,
|
|
49
|
+
userId,
|
|
50
|
+
keyId,
|
|
51
|
+
statusCode: res.statusCode,
|
|
52
|
+
duration,
|
|
53
|
+
ip: clientIp,
|
|
54
|
+
userAgent: req.headers['user-agent']?.slice(0, 100),
|
|
55
|
+
// DO NOT log: request body, auth headers, query params (may contain API keys or secrets)
|
|
56
|
+
});
|
|
57
|
+
// ── Fire-and-forget DB write ────────────────────────────────────────────
|
|
58
|
+
// Only runs when DATABASE_URL is set. Never slows down the response.
|
|
59
|
+
if (process.env.DATABASE_URL) {
|
|
60
|
+
const dbUserId = userId !== 'anonymous' ? userId : null;
|
|
61
|
+
const urlParam = typeof req.query?.url === 'string' ? req.query.url.slice(0, 2048) : null;
|
|
62
|
+
getAuditPool()
|
|
63
|
+
.query(`INSERT INTO usage_logs
|
|
64
|
+
(user_id, url, endpoint, method, status_code, processing_time_ms, ip_address, created_at)
|
|
65
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`, [dbUserId, urlParam, req.path, req.method, res.statusCode, duration, clientIp])
|
|
66
|
+
.catch(() => {
|
|
67
|
+
// Ignore audit DB failures — never propagate to the request
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
next();
|
|
73
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key authentication middleware with SOFT LIMIT enforcement
|
|
3
|
+
*
|
|
4
|
+
* Philosophy: Never fully block users. When weekly limits are exceeded,
|
|
5
|
+
* degrade to HTTP-only mode instead of returning 429.
|
|
6
|
+
* BURST limits (hourly) are HARD limits and return 429.
|
|
7
|
+
*
|
|
8
|
+
* Dual auth: Accepts both API keys AND JWT session tokens.
|
|
9
|
+
* API keys are validated via the auth store; JWTs are verified with JWT_SECRET.
|
|
10
|
+
* Dashboard pages use JWT tokens; CLI/SDK users use API keys.
|
|
11
|
+
*/
|
|
12
|
+
import { Request, Response, NextFunction } from 'express';
|
|
13
|
+
import { AuthStore, ApiKeyInfo } from '../auth-store.js';
|
|
14
|
+
import { KeyScope } from '../pg-auth-store.js';
|
|
15
|
+
import '../types.js';
|
|
16
|
+
declare global {
|
|
17
|
+
namespace Express {
|
|
18
|
+
interface Request {
|
|
19
|
+
auth?: {
|
|
20
|
+
keyInfo: ApiKeyInfo | null;
|
|
21
|
+
tier: 'free' | 'starter' | 'pro' | 'enterprise' | 'max' | 'admin';
|
|
22
|
+
rateLimit: number;
|
|
23
|
+
softLimited: boolean;
|
|
24
|
+
extraUsageAvailable: boolean;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Permission scope of the authenticated API key.
|
|
28
|
+
* Undefined when authenticated via JWT (dashboard session) — JWT users bypass scope enforcement.
|
|
29
|
+
* Set to 'full' | 'read' | 'restricted' for API key requests.
|
|
30
|
+
*/
|
|
31
|
+
keyScope?: KeyScope;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export declare function createAuthMiddleware(authStore: AuthStore): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key authentication middleware with SOFT LIMIT enforcement
|
|
3
|
+
*
|
|
4
|
+
* Philosophy: Never fully block users. When weekly limits are exceeded,
|
|
5
|
+
* degrade to HTTP-only mode instead of returning 429.
|
|
6
|
+
* BURST limits (hourly) are HARD limits and return 429.
|
|
7
|
+
*
|
|
8
|
+
* Dual auth: Accepts both API keys AND JWT session tokens.
|
|
9
|
+
* API keys are validated via the auth store; JWTs are verified with JWT_SECRET.
|
|
10
|
+
* Dashboard pages use JWT tokens; CLI/SDK users use API keys.
|
|
11
|
+
*/
|
|
12
|
+
import jwt from 'jsonwebtoken';
|
|
13
|
+
import { PostgresAuthStore } from '../pg-auth-store.js';
|
|
14
|
+
import '../types.js'; // Augments Express.Request with requestId
|
|
15
|
+
export function createAuthMiddleware(authStore) {
|
|
16
|
+
return async (req, res, next) => {
|
|
17
|
+
try {
|
|
18
|
+
// Extract API key from Authorization header or X-API-Key header
|
|
19
|
+
const authHeader = req.headers.authorization;
|
|
20
|
+
const apiKeyHeader = req.headers['x-api-key'];
|
|
21
|
+
// SECURITY: Skip API key auth for public/JWT-protected endpoints
|
|
22
|
+
// These routes either need no auth or use their own JWT middleware
|
|
23
|
+
const isPublicEndpoint = req.path === '/health' || req.path === '/ready' ||
|
|
24
|
+
req.path.startsWith('/v1/auth/') ||
|
|
25
|
+
req.path === '/v1/webhooks/stripe' ||
|
|
26
|
+
req.path === '/v1/me' ||
|
|
27
|
+
req.path.startsWith('/v1/keys') ||
|
|
28
|
+
req.path.startsWith('/v1/extra-usage');
|
|
29
|
+
if (isPublicEndpoint) {
|
|
30
|
+
req.auth = {
|
|
31
|
+
keyInfo: null,
|
|
32
|
+
tier: 'free',
|
|
33
|
+
rateLimit: 10,
|
|
34
|
+
softLimited: false,
|
|
35
|
+
extraUsageAvailable: false,
|
|
36
|
+
};
|
|
37
|
+
return next();
|
|
38
|
+
}
|
|
39
|
+
// SECURITY: Ensure headers are strings before processing (guards against
|
|
40
|
+
// malformed multi-value headers that would cause a null reference crash → 502)
|
|
41
|
+
const rawAuth = typeof authHeader === 'string' ? authHeader : undefined;
|
|
42
|
+
const rawApiKey = typeof apiKeyHeader === 'string' ? apiKeyHeader : undefined;
|
|
43
|
+
let apiKey = null;
|
|
44
|
+
if (rawAuth?.startsWith('Bearer ')) {
|
|
45
|
+
apiKey = rawAuth.slice(7);
|
|
46
|
+
}
|
|
47
|
+
else if (rawApiKey) {
|
|
48
|
+
apiKey = rawApiKey;
|
|
49
|
+
}
|
|
50
|
+
if (!apiKey) {
|
|
51
|
+
// No credentials supplied — let the request through unauthenticated.
|
|
52
|
+
// Public routes (/health, /v1/auth/*) already returned above.
|
|
53
|
+
// Protected routes must check req.auth?.keyInfo and return 401 themselves.
|
|
54
|
+
req.auth = undefined;
|
|
55
|
+
return next();
|
|
56
|
+
}
|
|
57
|
+
// Validate API key if provided
|
|
58
|
+
let keyInfo = null;
|
|
59
|
+
let softLimited = false;
|
|
60
|
+
let extraUsageAvailable = false;
|
|
61
|
+
if (apiKey) {
|
|
62
|
+
keyInfo = await authStore.validateKey(apiKey);
|
|
63
|
+
if (!keyInfo) {
|
|
64
|
+
// Check if key exists but is expired (before falling through to JWT)
|
|
65
|
+
if (authStore instanceof PostgresAuthStore) {
|
|
66
|
+
const expired = await authStore.isKeyExpired(apiKey);
|
|
67
|
+
if (expired) {
|
|
68
|
+
res.status(401).json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: {
|
|
71
|
+
type: 'key_expired',
|
|
72
|
+
message: 'This API key has expired.',
|
|
73
|
+
hint: 'Generate a new key at https://app.webpeel.dev/keys',
|
|
74
|
+
docs: 'https://webpeel.dev/docs/errors#unauthorized',
|
|
75
|
+
},
|
|
76
|
+
metadata: { requestId: req.requestId },
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// API key not found — try JWT session token as fallback.
|
|
82
|
+
// Dashboard uses JWT tokens (from /v1/auth/login or /v1/auth/oauth),
|
|
83
|
+
// while CLI/SDK users use API keys. Support both seamlessly.
|
|
84
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
85
|
+
if (jwtSecret) {
|
|
86
|
+
try {
|
|
87
|
+
const payload = jwt.verify(apiKey, jwtSecret);
|
|
88
|
+
if (payload.userId) {
|
|
89
|
+
// Valid JWT — treat as authenticated session user.
|
|
90
|
+
// Set req.auth with null keyInfo (no API key) and attach
|
|
91
|
+
// the JWT payload so routes can use req.user.userId.
|
|
92
|
+
req.auth = {
|
|
93
|
+
keyInfo: null,
|
|
94
|
+
tier: payload.tier || 'free',
|
|
95
|
+
rateLimit: 25,
|
|
96
|
+
softLimited: false,
|
|
97
|
+
extraUsageAvailable: false,
|
|
98
|
+
};
|
|
99
|
+
req.user = payload;
|
|
100
|
+
return next();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
if (process.env.DEBUG)
|
|
105
|
+
console.debug('[webpeel]', 'jwt verify failed:', e instanceof Error ? e.message : e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Firecrawl compat routes expect { error: "string" } not { error: { type, message } }
|
|
109
|
+
const isCompatRoute = /^\/(v[12]\/(scrape|crawl|map|search))/.test(req.path);
|
|
110
|
+
if (isCompatRoute) {
|
|
111
|
+
res.status(401).json({
|
|
112
|
+
success: false,
|
|
113
|
+
error: 'Invalid or expired API key. Get one at https://app.webpeel.dev/keys',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
res.status(401).json({
|
|
118
|
+
success: false,
|
|
119
|
+
error: {
|
|
120
|
+
type: 'invalid_key',
|
|
121
|
+
message: 'Invalid or expired API key.',
|
|
122
|
+
hint: 'Check your key at https://app.webpeel.dev/keys or generate a new one.',
|
|
123
|
+
docs: 'https://webpeel.dev/docs/errors#unauthorized',
|
|
124
|
+
},
|
|
125
|
+
metadata: { requestId: req.requestId },
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Check limits (only for PostgresAuthStore, skip for admin tier)
|
|
131
|
+
if (authStore instanceof PostgresAuthStore && keyInfo?.tier !== 'admin') {
|
|
132
|
+
// HARD LIMIT: Check burst limit first (per-hour cap)
|
|
133
|
+
const { allowed: burstAllowed, burst } = await authStore.checkBurstLimit(apiKey);
|
|
134
|
+
if (!burstAllowed) {
|
|
135
|
+
// Burst limit exceeded - HARD 429 with Retry-After
|
|
136
|
+
const retryAfterSeconds = 60 * parseInt(burst.resetsIn.match(/\d+/)?.[0] || '1', 10);
|
|
137
|
+
res.setHeader('Retry-After', retryAfterSeconds.toString());
|
|
138
|
+
res.setHeader('X-Burst-Limit', burst.limit.toString());
|
|
139
|
+
res.setHeader('X-Burst-Used', burst.count.toString());
|
|
140
|
+
const tier = keyInfo?.tier || 'free';
|
|
141
|
+
const upgradeHint = tier === 'free'
|
|
142
|
+
? ' Upgrade to Pro ($9/mo) for 100/hr → https://webpeel.dev/pricing'
|
|
143
|
+
: tier === 'pro'
|
|
144
|
+
? ' Upgrade to Max ($29/mo) for 500/hr → https://webpeel.dev/pricing'
|
|
145
|
+
: '';
|
|
146
|
+
res.status(429).json({
|
|
147
|
+
success: false,
|
|
148
|
+
error: {
|
|
149
|
+
type: 'burst_limit_exceeded',
|
|
150
|
+
message: `Hourly burst limit exceeded (${burst.count}/${burst.limit} on ${tier} plan). Resets in ${burst.resetsIn}.`,
|
|
151
|
+
hint: `Retry after ${burst.resetsIn}.${upgradeHint}`,
|
|
152
|
+
docs: 'https://webpeel.dev/docs/errors#rate-limited',
|
|
153
|
+
},
|
|
154
|
+
metadata: { requestId: req.requestId },
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Add burst headers
|
|
159
|
+
res.setHeader('X-Burst-Limit', burst.limit.toString());
|
|
160
|
+
res.setHeader('X-Burst-Used', burst.count.toString());
|
|
161
|
+
res.setHeader('X-Burst-Remaining', burst.remaining.toString());
|
|
162
|
+
// SOFT LIMIT: Check weekly usage
|
|
163
|
+
const { allowed, usage } = await authStore.checkLimit(apiKey);
|
|
164
|
+
// Check if extra usage is available
|
|
165
|
+
if (!allowed) {
|
|
166
|
+
extraUsageAvailable = await authStore.canUseExtraUsage(apiKey);
|
|
167
|
+
if (!extraUsageAvailable) {
|
|
168
|
+
// Over weekly quota, no extra usage — SOFT LIMIT (degrade to HTTP-only)
|
|
169
|
+
softLimited = true;
|
|
170
|
+
res.setHeader('X-Soft-Limited', 'true');
|
|
171
|
+
res.setHeader('X-Soft-Limit-Reason', 'Weekly quota exceeded. Requests degraded to HTTP-only mode.');
|
|
172
|
+
res.setHeader('X-Upgrade-URL', 'https://webpeel.dev/pricing');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Add weekly usage headers
|
|
176
|
+
if (usage) {
|
|
177
|
+
res.setHeader('X-Weekly-Limit', usage.totalAvailable.toString());
|
|
178
|
+
res.setHeader('X-Weekly-Used', usage.totalUsed.toString());
|
|
179
|
+
res.setHeader('X-Weekly-Remaining', Math.max(0, usage.remaining).toString());
|
|
180
|
+
res.setHeader('X-Weekly-Percent', usage.percentUsed.toString());
|
|
181
|
+
res.setHeader('X-Weekly-Resets-At', usage.resetsAt);
|
|
182
|
+
// Warn if over 80% usage and not using extra usage
|
|
183
|
+
if (usage.percentUsed >= 80 && !softLimited && !extraUsageAvailable) {
|
|
184
|
+
res.setHeader('X-Usage-Warning', `You've used ${usage.percentUsed}% of your weekly quota. Consider upgrading at https://webpeel.dev/pricing`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// Add extra usage headers if available
|
|
188
|
+
const extraInfo = await authStore.getExtraUsageInfo(apiKey);
|
|
189
|
+
if (extraInfo) {
|
|
190
|
+
res.setHeader('X-Extra-Usage-Enabled', extraInfo.enabled ? 'true' : 'false');
|
|
191
|
+
res.setHeader('X-Extra-Usage-Balance', extraInfo.balance.toFixed(2));
|
|
192
|
+
if (extraInfo.enabled) {
|
|
193
|
+
res.setHeader('X-Extra-Usage-Spent', extraInfo.spent.toFixed(2));
|
|
194
|
+
res.setHeader('X-Extra-Usage-Limit', extraInfo.spendingLimit.toFixed(2));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Set auth context on request
|
|
200
|
+
req.auth = {
|
|
201
|
+
keyInfo,
|
|
202
|
+
tier: keyInfo?.tier || 'free',
|
|
203
|
+
rateLimit: keyInfo?.rateLimit || 10,
|
|
204
|
+
softLimited,
|
|
205
|
+
extraUsageAvailable,
|
|
206
|
+
};
|
|
207
|
+
// Attach API key scope (only for API key auth; JWT users get undefined = bypass scope checks)
|
|
208
|
+
if (keyInfo) {
|
|
209
|
+
req.keyScope = keyInfo.scope || 'full';
|
|
210
|
+
}
|
|
211
|
+
next();
|
|
212
|
+
}
|
|
213
|
+
catch (_error) {
|
|
214
|
+
res.status(500).json({
|
|
215
|
+
success: false,
|
|
216
|
+
error: {
|
|
217
|
+
type: 'internal_error',
|
|
218
|
+
message: 'Authentication failed',
|
|
219
|
+
docs: 'https://webpeel.dev/docs/errors#internal-error',
|
|
220
|
+
},
|
|
221
|
+
metadata: { requestId: req.requestId },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-backed rate limiting middleware.
|
|
3
|
+
*
|
|
4
|
+
* Uses `rate-limiter-flexible` for atomic Redis operations — works correctly
|
|
5
|
+
* across all 6 K8s API pods (shared state via Redis).
|
|
6
|
+
*
|
|
7
|
+
* Falls back to in-memory when REDIS_URL is not set (CLI/local dev).
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Per-key (API key or IP) rate limiting
|
|
11
|
+
* - Tier-based limits: free=50/hr, pro=100/hr, max=500/hr
|
|
12
|
+
* - Route-based cost weighting: crawl=5x, render=3x, batch/screenshot=2x
|
|
13
|
+
* - Per-IP rate limiting ON TOP of API key limits (abuse prevention)
|
|
14
|
+
* - Exempt paths for health/docs endpoints
|
|
15
|
+
*/
|
|
16
|
+
import { Request, Response, NextFunction } from 'express';
|
|
17
|
+
import '../types.js';
|
|
18
|
+
export declare class RateLimiter {
|
|
19
|
+
private keyLimiter;
|
|
20
|
+
private ipLimiter;
|
|
21
|
+
private windowMs;
|
|
22
|
+
private initialized;
|
|
23
|
+
private initPromise;
|
|
24
|
+
constructor(windowMs?: number);
|
|
25
|
+
private init;
|
|
26
|
+
waitForInit(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Check if request is allowed.
|
|
29
|
+
* Returns { allowed, remaining, retryAfter? }
|
|
30
|
+
*/
|
|
31
|
+
checkLimit(identifier: string, limit: number, cost?: number): Promise<{
|
|
32
|
+
allowed: boolean;
|
|
33
|
+
remaining: number;
|
|
34
|
+
retryAfter?: number;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Check per-IP limit (global cap regardless of API key).
|
|
38
|
+
*/
|
|
39
|
+
checkIpLimit(ip: string, cost?: number): Promise<{
|
|
40
|
+
allowed: boolean;
|
|
41
|
+
remaining: number;
|
|
42
|
+
retryAfter?: number;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Backward compat: cleanup is no-op for Redis (TTL handles it).
|
|
46
|
+
* Kept for in-memory fallback and for app.ts which calls it on an interval.
|
|
47
|
+
*/
|
|
48
|
+
cleanup(): void;
|
|
49
|
+
}
|
|
50
|
+
export declare function createRateLimitMiddleware(limiter: RateLimiter): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|