@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,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebPeel WatchManager — Database-backed persistent URL monitoring
|
|
3
|
+
*
|
|
4
|
+
* Stores watch entries in PostgreSQL, periodically fetches watched URLs,
|
|
5
|
+
* compares content fingerprints to detect changes, and fires webhook
|
|
6
|
+
* notifications when a page is updated.
|
|
7
|
+
*
|
|
8
|
+
* This module is complementary to the in-process `watch.ts` poller:
|
|
9
|
+
* - `watch.ts` → ephemeral, CLI/in-process, no DB
|
|
10
|
+
* - `watch-manager.ts` → persistent, server-side, PostgreSQL-backed
|
|
11
|
+
*/
|
|
12
|
+
import { createHash, createHmac } from 'crypto';
|
|
13
|
+
import { fetch as undiciFetch } from 'undici';
|
|
14
|
+
/**
|
|
15
|
+
* Compute a simple Set-based line diff between two content strings.
|
|
16
|
+
*
|
|
17
|
+
* Splits both versions by newline, filters blank lines, then computes
|
|
18
|
+
* symmetric differences to find added/removed lines. Change percentage
|
|
19
|
+
* is relative to the larger of the two line-count sets.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const d = computeLineDiff('line1\nline2', 'line1\nline2\nline3');
|
|
24
|
+
* d.added // ['line3']
|
|
25
|
+
* d.removed // []
|
|
26
|
+
* d.changed // true
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function computeLineDiff(oldContent, newContent) {
|
|
30
|
+
const oldLines = new Set(oldContent.split('\n').filter(l => l.trim()));
|
|
31
|
+
const newLines = new Set(newContent.split('\n').filter(l => l.trim()));
|
|
32
|
+
const added = [...newLines].filter(l => !oldLines.has(l));
|
|
33
|
+
const removed = [...oldLines].filter(l => !newLines.has(l));
|
|
34
|
+
const maxLen = Math.max(oldLines.size, newLines.size);
|
|
35
|
+
const changePercent = maxLen === 0 ? 0 : Math.min(100, Math.round(((added.length + removed.length) / maxLen) * 100));
|
|
36
|
+
const changed = added.length > 0 || removed.length > 0;
|
|
37
|
+
let summary;
|
|
38
|
+
if (!changed) {
|
|
39
|
+
summary = 'No changes detected.';
|
|
40
|
+
}
|
|
41
|
+
else if (changePercent < 5) {
|
|
42
|
+
summary = `Minor wording changes (${changePercent}% changed)`;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const parts = [];
|
|
46
|
+
if (added.length > 0)
|
|
47
|
+
parts.push(`${added.length} line${added.length !== 1 ? 's' : ''} added`);
|
|
48
|
+
if (removed.length > 0)
|
|
49
|
+
parts.push(`${removed.length} line${removed.length !== 1 ? 's' : ''} removed`);
|
|
50
|
+
summary = parts.join(', ');
|
|
51
|
+
}
|
|
52
|
+
return { changed, added, removed, summary, changePercent };
|
|
53
|
+
}
|
|
54
|
+
// ─── Internal helpers ──────────────────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Compute a stable SHA-256 fingerprint of page content.
|
|
57
|
+
* Normalises whitespace so cosmetic-only reformatting doesn't trigger alerts.
|
|
58
|
+
*/
|
|
59
|
+
export function computeFingerprint(content) {
|
|
60
|
+
const normalized = content.trim().replace(/\s+/g, ' ');
|
|
61
|
+
return createHash('sha256').update(normalized).digest('hex');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Paragraph-level diff — splits both versions of a page into paragraph blocks
|
|
65
|
+
* (separated by blank lines), then finds paragraphs that appear exclusively in
|
|
66
|
+
* each version. Only blocks longer than 10 characters are considered to avoid
|
|
67
|
+
* noise from short punctuation-only lines.
|
|
68
|
+
*/
|
|
69
|
+
export function computeParagraphDiff(oldContent, newContent) {
|
|
70
|
+
const toSet = (text) => new Set(text
|
|
71
|
+
.split(/\n{2,}/)
|
|
72
|
+
.map(p => p.trim())
|
|
73
|
+
.filter(p => p.length > 10));
|
|
74
|
+
const oldSet = toSet(oldContent);
|
|
75
|
+
const newSet = toSet(newContent);
|
|
76
|
+
const addedText = [];
|
|
77
|
+
const removedText = [];
|
|
78
|
+
for (const p of newSet) {
|
|
79
|
+
if (!oldSet.has(p))
|
|
80
|
+
addedText.push(p.slice(0, 500));
|
|
81
|
+
}
|
|
82
|
+
for (const p of oldSet) {
|
|
83
|
+
if (!newSet.has(p))
|
|
84
|
+
removedText.push(p.slice(0, 500));
|
|
85
|
+
}
|
|
86
|
+
return { addedText, removedText };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Sign a webhook payload body with HMAC-SHA256.
|
|
90
|
+
*
|
|
91
|
+
* @param body - The raw JSON string that will be sent as the request body.
|
|
92
|
+
* @param secret - The signing secret shared between WebPeel and the recipient.
|
|
93
|
+
* @returns - Hex digest of the HMAC-SHA256 signature.
|
|
94
|
+
*
|
|
95
|
+
* Recipients verify delivery authenticity like this:
|
|
96
|
+
* const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
|
|
97
|
+
* if (receivedSignature !== `sha256=${expected}`) reject(); // tampered or wrong secret
|
|
98
|
+
*/
|
|
99
|
+
function signWebhookBody(body, secret) {
|
|
100
|
+
return createHmac('sha256', secret).update(body).digest('hex');
|
|
101
|
+
}
|
|
102
|
+
/** Post a JSON payload to a webhook URL, silently swallowing delivery errors. */
|
|
103
|
+
async function sendWatchWebhook(webhookUrl, payload, webhookSecret) {
|
|
104
|
+
try {
|
|
105
|
+
const bodyStr = JSON.stringify(payload);
|
|
106
|
+
const headers = {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
'User-Agent': 'WebPeel-Watch/1.0 (+https://webpeel.dev)',
|
|
109
|
+
};
|
|
110
|
+
// Sign the payload when a secret is available (per-watch secret or global fallback).
|
|
111
|
+
const secret = webhookSecret || process.env.WEBHOOK_SIGNING_SECRET;
|
|
112
|
+
if (secret) {
|
|
113
|
+
headers['X-WebPeel-Signature'] = `sha256=${signWebhookBody(bodyStr, secret)}`;
|
|
114
|
+
headers['X-WebPeel-Timestamp'] = String(Date.now());
|
|
115
|
+
}
|
|
116
|
+
await undiciFetch(webhookUrl, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers,
|
|
119
|
+
body: bodyStr,
|
|
120
|
+
signal: AbortSignal.timeout(10_000),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
process.stderr.write(`[watch-manager] Webhook delivery failed to ${webhookUrl}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Map a raw database row to a typed {@link WatchEntry}. */
|
|
128
|
+
function rowToEntry(row) {
|
|
129
|
+
return {
|
|
130
|
+
id: row.id,
|
|
131
|
+
accountId: row.account_id,
|
|
132
|
+
url: row.url,
|
|
133
|
+
webhookUrl: row.webhook_url ?? undefined,
|
|
134
|
+
webhookSecret: row.webhook_secret ?? undefined,
|
|
135
|
+
checkIntervalMinutes: row.check_interval_minutes ?? 60,
|
|
136
|
+
selector: row.selector ?? undefined,
|
|
137
|
+
lastFingerprint: row.last_fingerprint ?? undefined,
|
|
138
|
+
lastCheckedAt: row.last_checked_at ? new Date(row.last_checked_at) : undefined,
|
|
139
|
+
lastChangedAt: row.last_changed_at ? new Date(row.last_changed_at) : undefined,
|
|
140
|
+
changeCount: row.change_count ?? 0,
|
|
141
|
+
status: row.status ?? 'active',
|
|
142
|
+
errorMessage: row.error_message ?? undefined,
|
|
143
|
+
createdAt: new Date(row.created_at),
|
|
144
|
+
updatedAt: new Date(row.updated_at),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// ─── WatchManager ──────────────────────────────────────────────────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Database-backed URL watch manager.
|
|
150
|
+
*
|
|
151
|
+
* Stores watch entries in a PostgreSQL `watches` table (see
|
|
152
|
+
* `migrations/007_watch.sql`) and handles periodic checks, change detection,
|
|
153
|
+
* and webhook delivery.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const manager = new WatchManager(pool);
|
|
158
|
+
* const watch = await manager.create('acct-uuid', 'https://example.com/pricing', {
|
|
159
|
+
* webhookUrl: 'https://hooks.example.com/alert',
|
|
160
|
+
* checkIntervalMinutes: 30,
|
|
161
|
+
* });
|
|
162
|
+
* const diff = await manager.check(watch.id);
|
|
163
|
+
* console.log(diff.summary);
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export class WatchManager {
|
|
167
|
+
db;
|
|
168
|
+
constructor(db) {
|
|
169
|
+
this.db = db;
|
|
170
|
+
}
|
|
171
|
+
// ─── CRUD ────────────────────────────────────────────────────────────────────
|
|
172
|
+
/**
|
|
173
|
+
* Create a new watch entry for the given URL.
|
|
174
|
+
* The watch is immediately active; the first check will establish the baseline.
|
|
175
|
+
*/
|
|
176
|
+
async create(accountId, url, options = {}) {
|
|
177
|
+
const { webhookUrl, webhookSecret, checkIntervalMinutes = 60, selector } = options;
|
|
178
|
+
const result = await this.db.query(`INSERT INTO watches (account_id, url, webhook_url, webhook_secret, check_interval_minutes, selector)
|
|
179
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
180
|
+
RETURNING *`, [accountId, url, webhookUrl ?? null, webhookSecret ?? null, checkIntervalMinutes, selector ?? null]);
|
|
181
|
+
return rowToEntry(result.rows[0]);
|
|
182
|
+
}
|
|
183
|
+
/** List all watches owned by the given account, most recent first. */
|
|
184
|
+
async list(accountId) {
|
|
185
|
+
const result = await this.db.query(`SELECT * FROM watches WHERE account_id = $1 ORDER BY created_at DESC`, [accountId]);
|
|
186
|
+
return result.rows.map(rowToEntry);
|
|
187
|
+
}
|
|
188
|
+
/** Get a single watch by ID, or null if not found. */
|
|
189
|
+
async get(watchId) {
|
|
190
|
+
const result = await this.db.query(`SELECT * FROM watches WHERE id = $1`, [watchId]);
|
|
191
|
+
if (result.rows.length === 0)
|
|
192
|
+
return null;
|
|
193
|
+
return rowToEntry(result.rows[0]);
|
|
194
|
+
}
|
|
195
|
+
/** Pause a watch — it will not be included in {@link checkDue} runs. */
|
|
196
|
+
async pause(watchId) {
|
|
197
|
+
await this.db.query(`UPDATE watches SET status = 'paused', updated_at = NOW() WHERE id = $1`, [watchId]);
|
|
198
|
+
}
|
|
199
|
+
/** Resume a previously paused (or errored) watch. */
|
|
200
|
+
async resume(watchId) {
|
|
201
|
+
await this.db.query(`UPDATE watches
|
|
202
|
+
SET status = 'active', error_message = NULL, updated_at = NOW()
|
|
203
|
+
WHERE id = $1`, [watchId]);
|
|
204
|
+
}
|
|
205
|
+
/** Permanently delete a watch. */
|
|
206
|
+
async delete(watchId) {
|
|
207
|
+
await this.db.query(`DELETE FROM watches WHERE id = $1`, [watchId]);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Update mutable properties of a watch.
|
|
211
|
+
* Only the fields present in `updates` are changed.
|
|
212
|
+
*/
|
|
213
|
+
async update(watchId, updates) {
|
|
214
|
+
const setClauses = ['updated_at = NOW()'];
|
|
215
|
+
const values = [];
|
|
216
|
+
let idx = 1;
|
|
217
|
+
if ('webhookUrl' in updates) {
|
|
218
|
+
setClauses.push(`webhook_url = $${idx++}`);
|
|
219
|
+
values.push(updates.webhookUrl ?? null);
|
|
220
|
+
}
|
|
221
|
+
if ('webhookSecret' in updates) {
|
|
222
|
+
setClauses.push(`webhook_secret = $${idx++}`);
|
|
223
|
+
values.push(updates.webhookSecret ?? null);
|
|
224
|
+
}
|
|
225
|
+
if ('checkIntervalMinutes' in updates) {
|
|
226
|
+
setClauses.push(`check_interval_minutes = $${idx++}`);
|
|
227
|
+
values.push(updates.checkIntervalMinutes);
|
|
228
|
+
}
|
|
229
|
+
if ('selector' in updates) {
|
|
230
|
+
setClauses.push(`selector = $${idx++}`);
|
|
231
|
+
values.push(updates.selector ?? null);
|
|
232
|
+
}
|
|
233
|
+
if ('status' in updates) {
|
|
234
|
+
setClauses.push(`status = $${idx++}`);
|
|
235
|
+
values.push(updates.status);
|
|
236
|
+
}
|
|
237
|
+
values.push(watchId);
|
|
238
|
+
const result = await this.db.query(`UPDATE watches SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`, values);
|
|
239
|
+
if (result.rows.length === 0)
|
|
240
|
+
return null;
|
|
241
|
+
return rowToEntry(result.rows[0]);
|
|
242
|
+
}
|
|
243
|
+
// ─── Checking ────────────────────────────────────────────────────────────────
|
|
244
|
+
/**
|
|
245
|
+
* Perform an immediate content check for the given watch ID.
|
|
246
|
+
*
|
|
247
|
+
* Steps:
|
|
248
|
+
* 1. Load watch entry and previous content snapshot from disk (if any).
|
|
249
|
+
* 2. Fetch the current page via `peel()`.
|
|
250
|
+
* 3. Compute a SHA-256 fingerprint of the normalised content.
|
|
251
|
+
* 4. If the fingerprint changed, compute a paragraph-level diff and fire the webhook.
|
|
252
|
+
* 5. Persist `last_fingerprint`, `last_checked_at`, and `change_count` to the DB.
|
|
253
|
+
* 6. Return a {@link WatchDiff} describing what changed.
|
|
254
|
+
*/
|
|
255
|
+
async check(watchId) {
|
|
256
|
+
const watch = await this.get(watchId);
|
|
257
|
+
if (!watch)
|
|
258
|
+
throw new Error(`Watch not found: ${watchId}`);
|
|
259
|
+
const now = new Date();
|
|
260
|
+
try {
|
|
261
|
+
// Load previous content snapshot for text-diff computation.
|
|
262
|
+
const { getSnapshot } = await import('./change-tracking.js');
|
|
263
|
+
const prevSnapshot = await getSnapshot(watch.url);
|
|
264
|
+
// Fetch current content.
|
|
265
|
+
const { peel } = await import('../index.js');
|
|
266
|
+
const peelResult = await peel(watch.url, {
|
|
267
|
+
format: 'markdown',
|
|
268
|
+
selector: watch.selector,
|
|
269
|
+
timeout: 30_000,
|
|
270
|
+
// Enable change tracking so the snapshot is persisted for future diffs.
|
|
271
|
+
changeTracking: true,
|
|
272
|
+
});
|
|
273
|
+
const currentContent = peelResult.content;
|
|
274
|
+
const currentFingerprint = computeFingerprint(currentContent);
|
|
275
|
+
const previousFingerprint = watch.lastFingerprint ?? '';
|
|
276
|
+
// Determine whether content actually changed relative to our DB record.
|
|
277
|
+
const isFirstCheck = !previousFingerprint;
|
|
278
|
+
const changed = !isFirstCheck && currentFingerprint !== previousFingerprint;
|
|
279
|
+
// Compute text diff when changed and we have old content to compare against.
|
|
280
|
+
let addedText = [];
|
|
281
|
+
let removedText = [];
|
|
282
|
+
let summary;
|
|
283
|
+
if (isFirstCheck) {
|
|
284
|
+
summary = 'Baseline fingerprint established — monitoring active.';
|
|
285
|
+
}
|
|
286
|
+
else if (changed) {
|
|
287
|
+
const oldContent = prevSnapshot?.content ?? '';
|
|
288
|
+
if (oldContent) {
|
|
289
|
+
const diff = computeParagraphDiff(oldContent, currentContent);
|
|
290
|
+
addedText = diff.addedText;
|
|
291
|
+
removedText = diff.removedText;
|
|
292
|
+
summary =
|
|
293
|
+
addedText.length > 0 || removedText.length > 0
|
|
294
|
+
? `Page updated: ${addedText.length} block${addedText.length !== 1 ? 's' : ''} added, ` +
|
|
295
|
+
`${removedText.length} block${removedText.length !== 1 ? 's' : ''} removed.`
|
|
296
|
+
: 'Page content changed (fingerprint mismatch — no paragraph-level diff available).';
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
summary = 'Page content changed (no previous snapshot available for text diff).';
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
summary = 'No changes detected.';
|
|
304
|
+
}
|
|
305
|
+
// Update DB.
|
|
306
|
+
if (changed) {
|
|
307
|
+
await this.db.query(`UPDATE watches
|
|
308
|
+
SET last_fingerprint = $1,
|
|
309
|
+
last_checked_at = $2,
|
|
310
|
+
last_changed_at = $2,
|
|
311
|
+
change_count = change_count + 1,
|
|
312
|
+
status = 'active',
|
|
313
|
+
error_message = NULL,
|
|
314
|
+
updated_at = $2
|
|
315
|
+
WHERE id = $3`, [currentFingerprint, now, watchId]);
|
|
316
|
+
// Reload to get the latest change_count for the webhook payload.
|
|
317
|
+
const updated = await this.get(watchId);
|
|
318
|
+
// Fire webhook.
|
|
319
|
+
if (watch.webhookUrl && updated) {
|
|
320
|
+
const payload = {
|
|
321
|
+
event: 'watch.changed',
|
|
322
|
+
watchId: watch.id,
|
|
323
|
+
url: watch.url,
|
|
324
|
+
changedAt: now.toISOString(),
|
|
325
|
+
changeCount: updated.changeCount,
|
|
326
|
+
diff: { addedText, removedText, summary },
|
|
327
|
+
};
|
|
328
|
+
await sendWatchWebhook(watch.webhookUrl, payload, watch.webhookSecret);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
await this.db.query(`UPDATE watches
|
|
333
|
+
SET last_fingerprint = COALESCE($1, last_fingerprint),
|
|
334
|
+
last_checked_at = $2,
|
|
335
|
+
status = 'active',
|
|
336
|
+
error_message = NULL,
|
|
337
|
+
updated_at = $2
|
|
338
|
+
WHERE id = $3`, [currentFingerprint || null, now, watchId]);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
changed,
|
|
342
|
+
previousFingerprint,
|
|
343
|
+
currentFingerprint,
|
|
344
|
+
summary,
|
|
345
|
+
addedText,
|
|
346
|
+
removedText,
|
|
347
|
+
content: currentContent,
|
|
348
|
+
previousContent: prevSnapshot?.content ?? '',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
const errMsg = (error instanceof Error ? error.message : String(error)).slice(0, 500);
|
|
353
|
+
// Mark the watch as errored so operators can investigate.
|
|
354
|
+
await this.db.query(`UPDATE watches
|
|
355
|
+
SET status = 'error',
|
|
356
|
+
error_message = $1,
|
|
357
|
+
last_checked_at = $2,
|
|
358
|
+
updated_at = $2
|
|
359
|
+
WHERE id = $3`, [errMsg, now, watchId]);
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Scan the database for watches that are due for a check and run them.
|
|
365
|
+
*
|
|
366
|
+
* A watch is "due" when:
|
|
367
|
+
* - `status = 'active'`
|
|
368
|
+
* - `last_checked_at` is NULL (never checked) OR older than `check_interval_minutes`
|
|
369
|
+
*
|
|
370
|
+
* Processes up to 50 watches per invocation to avoid long-running cycles.
|
|
371
|
+
*/
|
|
372
|
+
async checkDue() {
|
|
373
|
+
const result = await this.db.query(`SELECT * FROM watches
|
|
374
|
+
WHERE status = 'active'
|
|
375
|
+
AND (
|
|
376
|
+
last_checked_at IS NULL
|
|
377
|
+
OR last_checked_at < NOW() - (check_interval_minutes * INTERVAL '1 minute')
|
|
378
|
+
)
|
|
379
|
+
ORDER BY last_checked_at ASC NULLS FIRST
|
|
380
|
+
LIMIT 50`);
|
|
381
|
+
for (const row of result.rows) {
|
|
382
|
+
const watch = rowToEntry(row);
|
|
383
|
+
try {
|
|
384
|
+
await this.check(watch.id);
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
process.stderr.write(`[watch-manager] Error checking watch ${watch.id} (${watch.url}): ` +
|
|
388
|
+
`${err instanceof Error ? err.message : String(err)}\n`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ─── Background checker ────────────────────────────────────────────────────────
|
|
394
|
+
/**
|
|
395
|
+
* Start a background interval that calls {@link WatchManager.checkDue} every
|
|
396
|
+
* minute. Wire this up in `app.ts` after the server starts.
|
|
397
|
+
*
|
|
398
|
+
* @returns The interval handle (pass to `clearInterval` for clean shutdown).
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```typescript
|
|
402
|
+
* const handle = startWatchChecker(pool);
|
|
403
|
+
* process.on('SIGTERM', () => clearInterval(handle));
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
export function startWatchChecker(db) {
|
|
407
|
+
const manager = new WatchManager(db);
|
|
408
|
+
return setInterval(async () => {
|
|
409
|
+
try {
|
|
410
|
+
await manager.checkDue();
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
process.stderr.write(`[watch-manager] Background checker error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
414
|
+
}
|
|
415
|
+
}, 60_000); // Every 1 minute
|
|
416
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebPeel Watch - Lightweight URL monitoring with assertions
|
|
3
|
+
*
|
|
4
|
+
* Polls a URL on a configurable interval, evaluates assertions against the
|
|
5
|
+
* response, detects content changes, and optionally fires a webhook on failure.
|
|
6
|
+
*/
|
|
7
|
+
export interface WatchOptions {
|
|
8
|
+
url: string;
|
|
9
|
+
/** Polling interval in milliseconds */
|
|
10
|
+
intervalMs: number;
|
|
11
|
+
/** Assertions to evaluate on every check */
|
|
12
|
+
assertions?: Assertion[];
|
|
13
|
+
/** POST this URL when an assertion fails or content changes */
|
|
14
|
+
webhookUrl?: string;
|
|
15
|
+
/** HMAC-SHA256 secret for signing webhook deliveries (header: X-WebPeel-Signature). */
|
|
16
|
+
webhookSecret?: string;
|
|
17
|
+
/** Per-request timeout in milliseconds (default: 10 000) */
|
|
18
|
+
timeout?: number;
|
|
19
|
+
/** Stop after this many checks (default: unlimited) */
|
|
20
|
+
maxChecks?: number;
|
|
21
|
+
/** Use browser rendering instead of simple HTTP fetch */
|
|
22
|
+
render?: boolean;
|
|
23
|
+
/** Output each result as NDJSON to stdout */
|
|
24
|
+
json?: boolean;
|
|
25
|
+
/** Suppress output unless a failure or change is detected */
|
|
26
|
+
silent?: boolean;
|
|
27
|
+
/** Optional callback invoked after every check */
|
|
28
|
+
onCheck?: (result: WatchCheckResult) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface Assertion {
|
|
31
|
+
/** Field to evaluate: "status" (HTTP status), "body", "header.<name>",
|
|
32
|
+
* or a dot-notation path into the JSON response body (e.g. "data.health") */
|
|
33
|
+
field: string;
|
|
34
|
+
operator: '=' | '!=' | '<' | '>' | 'contains';
|
|
35
|
+
value: string;
|
|
36
|
+
}
|
|
37
|
+
export interface WatchCheckResult {
|
|
38
|
+
timestamp: string;
|
|
39
|
+
url: string;
|
|
40
|
+
/** HTTP status code (0 on network error, 408 on timeout) */
|
|
41
|
+
status: number;
|
|
42
|
+
/** Request round-trip time in milliseconds */
|
|
43
|
+
elapsed: number;
|
|
44
|
+
/** Per-assertion evaluation results */
|
|
45
|
+
assertions: AssertionResult[];
|
|
46
|
+
/** true when every assertion passed (or there are no assertions) */
|
|
47
|
+
allPassed: boolean;
|
|
48
|
+
/** true when the response body changed since the previous check */
|
|
49
|
+
changed: boolean;
|
|
50
|
+
/** Present only on network/fetch errors */
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface AssertionResult {
|
|
54
|
+
field: string;
|
|
55
|
+
expected: string;
|
|
56
|
+
actual: string;
|
|
57
|
+
passed: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parse a human-readable duration string into milliseconds.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* parseDuration("30s") // → 30 000
|
|
64
|
+
* parseDuration("5m") // → 300 000
|
|
65
|
+
* parseDuration("1h") // → 3 600 000
|
|
66
|
+
* parseDuration("500ms")// → 500
|
|
67
|
+
*/
|
|
68
|
+
export declare function parseDuration(duration: string): number;
|
|
69
|
+
/**
|
|
70
|
+
* Parse an assertion expression into an {@link Assertion} object.
|
|
71
|
+
*
|
|
72
|
+
* Supported formats:
|
|
73
|
+
* field=value — equality
|
|
74
|
+
* field!=value — inequality
|
|
75
|
+
* field>value — greater-than (numeric)
|
|
76
|
+
* field<value — less-than (numeric)
|
|
77
|
+
* field contains str — substring match
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* parseAssertion("status=200") // HTTP status
|
|
81
|
+
* parseAssertion("body.status=healthy") // JSON field
|
|
82
|
+
* parseAssertion("version!=0.0.0")
|
|
83
|
+
*/
|
|
84
|
+
export declare function parseAssertion(expr: string): Assertion;
|
|
85
|
+
/**
|
|
86
|
+
* Monitor a URL on a recurring interval.
|
|
87
|
+
*
|
|
88
|
+
* Resolves when {@link WatchOptions.maxChecks} is reached (or never, until the
|
|
89
|
+
* process receives SIGINT/SIGTERM).
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* await watch({
|
|
94
|
+
* url: 'https://api.example.com/health',
|
|
95
|
+
* intervalMs: 60_000,
|
|
96
|
+
* assertions: [{ field: 'status', operator: '=', value: 'healthy' }],
|
|
97
|
+
* webhookUrl: 'https://hooks.example.com/alert',
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export declare function watch(options: WatchOptions): Promise<void>;
|