@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.
Files changed (547) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +313 -0
  3. package/dist/cache.d.ts +30 -0
  4. package/dist/cache.js +139 -0
  5. package/dist/cli/commands/auth.d.ts +5 -0
  6. package/dist/cli/commands/auth.js +411 -0
  7. package/dist/cli/commands/doctor.d.ts +37 -0
  8. package/dist/cli/commands/doctor.js +371 -0
  9. package/dist/cli/commands/fetch.d.ts +6 -0
  10. package/dist/cli/commands/fetch.js +1345 -0
  11. package/dist/cli/commands/guide.d.ts +2 -0
  12. package/dist/cli/commands/guide.js +183 -0
  13. package/dist/cli/commands/interact.d.ts +5 -0
  14. package/dist/cli/commands/interact.js +840 -0
  15. package/dist/cli/commands/jobs.d.ts +5 -0
  16. package/dist/cli/commands/jobs.js +997 -0
  17. package/dist/cli/commands/monitor.d.ts +12 -0
  18. package/dist/cli/commands/monitor.js +197 -0
  19. package/dist/cli/commands/observe.d.ts +12 -0
  20. package/dist/cli/commands/observe.js +158 -0
  21. package/dist/cli/commands/screenshot.d.ts +5 -0
  22. package/dist/cli/commands/screenshot.js +282 -0
  23. package/dist/cli/commands/search.d.ts +5 -0
  24. package/dist/cli/commands/search.js +1021 -0
  25. package/dist/cli/commands/setup.d.ts +13 -0
  26. package/dist/cli/commands/setup.js +244 -0
  27. package/dist/cli/commands/skill.d.ts +15 -0
  28. package/dist/cli/commands/skill.js +195 -0
  29. package/dist/cli/utils.d.ts +84 -0
  30. package/dist/cli/utils.js +806 -0
  31. package/dist/cli-auth.d.ts +75 -0
  32. package/dist/cli-auth.js +369 -0
  33. package/dist/cli.d.ts +17 -0
  34. package/dist/cli.js +99 -0
  35. package/dist/core/actions.d.ts +69 -0
  36. package/dist/core/actions.js +495 -0
  37. package/dist/core/agent.d.ts +98 -0
  38. package/dist/core/agent.js +558 -0
  39. package/dist/core/answer.d.ts +42 -0
  40. package/dist/core/answer.js +395 -0
  41. package/dist/core/application-tracker.d.ts +84 -0
  42. package/dist/core/application-tracker.js +184 -0
  43. package/dist/core/apply.d.ts +162 -0
  44. package/dist/core/apply.js +816 -0
  45. package/dist/core/auth-detection.d.ts +35 -0
  46. package/dist/core/auth-detection.js +358 -0
  47. package/dist/core/auto-extract.d.ts +82 -0
  48. package/dist/core/auto-extract.js +604 -0
  49. package/dist/core/auto-interact.d.ts +23 -0
  50. package/dist/core/auto-interact.js +246 -0
  51. package/dist/core/bm25-filter.d.ts +66 -0
  52. package/dist/core/bm25-filter.js +288 -0
  53. package/dist/core/branding.d.ts +54 -0
  54. package/dist/core/branding.js +234 -0
  55. package/dist/core/browser-fetch.d.ts +323 -0
  56. package/dist/core/browser-fetch.js +1600 -0
  57. package/dist/core/browser-pool.d.ts +91 -0
  58. package/dist/core/browser-pool.js +550 -0
  59. package/dist/core/budget.d.ts +42 -0
  60. package/dist/core/budget.js +324 -0
  61. package/dist/core/business-intel.d.ts +47 -0
  62. package/dist/core/business-intel.js +279 -0
  63. package/dist/core/cache.d.ts +13 -0
  64. package/dist/core/cache.js +121 -0
  65. package/dist/core/cf-worker-proxy.d.ts +32 -0
  66. package/dist/core/cf-worker-proxy.js +87 -0
  67. package/dist/core/challenge-detection.d.ts +26 -0
  68. package/dist/core/challenge-detection.js +468 -0
  69. package/dist/core/change-tracking.d.ts +75 -0
  70. package/dist/core/change-tracking.js +276 -0
  71. package/dist/core/chunker.d.ts +46 -0
  72. package/dist/core/chunker.js +249 -0
  73. package/dist/core/chunking.d.ts +42 -0
  74. package/dist/core/chunking.js +181 -0
  75. package/dist/core/circuit-breaker.d.ts +44 -0
  76. package/dist/core/circuit-breaker.js +85 -0
  77. package/dist/core/content-pruner.d.ts +47 -0
  78. package/dist/core/content-pruner.js +425 -0
  79. package/dist/core/cookie-cache.d.ts +60 -0
  80. package/dist/core/cookie-cache.js +163 -0
  81. package/dist/core/crawl-checkpoint.d.ts +54 -0
  82. package/dist/core/crawl-checkpoint.js +104 -0
  83. package/dist/core/crawler.d.ts +84 -0
  84. package/dist/core/crawler.js +349 -0
  85. package/dist/core/cross-verify.d.ts +27 -0
  86. package/dist/core/cross-verify.js +93 -0
  87. package/dist/core/deep-fetch.d.ts +74 -0
  88. package/dist/core/deep-fetch.js +405 -0
  89. package/dist/core/deep-research.d.ts +141 -0
  90. package/dist/core/deep-research.js +972 -0
  91. package/dist/core/design-analysis.d.ts +70 -0
  92. package/dist/core/design-analysis.js +490 -0
  93. package/dist/core/design-compare.d.ts +38 -0
  94. package/dist/core/design-compare.js +264 -0
  95. package/dist/core/diff.d.ts +61 -0
  96. package/dist/core/diff.js +289 -0
  97. package/dist/core/dns-cache.d.ts +20 -0
  98. package/dist/core/dns-cache.js +198 -0
  99. package/dist/core/documents.d.ts +23 -0
  100. package/dist/core/documents.js +123 -0
  101. package/dist/core/domain-memory.d.ts +66 -0
  102. package/dist/core/domain-memory.js +163 -0
  103. package/dist/core/domain-verify.d.ts +40 -0
  104. package/dist/core/domain-verify.js +379 -0
  105. package/dist/core/engine-ranker.d.ts +112 -0
  106. package/dist/core/engine-ranker.js +395 -0
  107. package/dist/core/extract-inline.d.ts +38 -0
  108. package/dist/core/extract-inline.js +215 -0
  109. package/dist/core/extract-listings.d.ts +38 -0
  110. package/dist/core/extract-listings.js +461 -0
  111. package/dist/core/extract.d.ts +9 -0
  112. package/dist/core/extract.js +139 -0
  113. package/dist/core/fetch-cache.d.ts +57 -0
  114. package/dist/core/fetch-cache.js +95 -0
  115. package/dist/core/fetcher.d.ts +13 -0
  116. package/dist/core/fetcher.js +12 -0
  117. package/dist/core/google-cache.d.ts +29 -0
  118. package/dist/core/google-cache.js +180 -0
  119. package/dist/core/google-serp-parser.d.ts +82 -0
  120. package/dist/core/google-serp-parser.js +287 -0
  121. package/dist/core/hotel-search.d.ts +122 -0
  122. package/dist/core/hotel-search.js +382 -0
  123. package/dist/core/http-fetch.d.ts +72 -0
  124. package/dist/core/http-fetch.js +820 -0
  125. package/dist/core/human.d.ts +175 -0
  126. package/dist/core/human.js +680 -0
  127. package/dist/core/image-caption.d.ts +44 -0
  128. package/dist/core/image-caption.js +271 -0
  129. package/dist/core/jobs.d.ts +75 -0
  130. package/dist/core/jobs.js +634 -0
  131. package/dist/core/json-ld.d.ts +15 -0
  132. package/dist/core/json-ld.js +617 -0
  133. package/dist/core/language-detect.d.ts +18 -0
  134. package/dist/core/language-detect.js +135 -0
  135. package/dist/core/links.d.ts +10 -0
  136. package/dist/core/links.js +44 -0
  137. package/dist/core/llm-extract.d.ts +71 -0
  138. package/dist/core/llm-extract.js +507 -0
  139. package/dist/core/llm-provider.d.ts +100 -0
  140. package/dist/core/llm-provider.js +702 -0
  141. package/dist/core/local-search.d.ts +60 -0
  142. package/dist/core/local-search.js +308 -0
  143. package/dist/core/logger.d.ts +28 -0
  144. package/dist/core/logger.js +104 -0
  145. package/dist/core/map.d.ts +33 -0
  146. package/dist/core/map.js +127 -0
  147. package/dist/core/markdown.d.ts +92 -0
  148. package/dist/core/markdown.js +809 -0
  149. package/dist/core/metadata.d.ts +34 -0
  150. package/dist/core/metadata.js +422 -0
  151. package/dist/core/observe.d.ts +113 -0
  152. package/dist/core/observe.js +395 -0
  153. package/dist/core/ocr.d.ts +12 -0
  154. package/dist/core/ocr.js +33 -0
  155. package/dist/core/paginate.d.ts +31 -0
  156. package/dist/core/paginate.js +106 -0
  157. package/dist/core/pdf.d.ts +8 -0
  158. package/dist/core/pdf.js +25 -0
  159. package/dist/core/peel-tls.d.ts +25 -0
  160. package/dist/core/peel-tls.js +220 -0
  161. package/dist/core/pipeline.d.ts +132 -0
  162. package/dist/core/pipeline.js +1666 -0
  163. package/dist/core/profiles.d.ts +61 -0
  164. package/dist/core/profiles.js +350 -0
  165. package/dist/core/prompt-guard.d.ts +30 -0
  166. package/dist/core/prompt-guard.js +119 -0
  167. package/dist/core/proxy-config.d.ts +90 -0
  168. package/dist/core/proxy-config.js +172 -0
  169. package/dist/core/quick-answer.d.ts +53 -0
  170. package/dist/core/quick-answer.js +833 -0
  171. package/dist/core/rate-governor.d.ts +80 -0
  172. package/dist/core/rate-governor.js +238 -0
  173. package/dist/core/readability.d.ts +57 -0
  174. package/dist/core/readability.js +533 -0
  175. package/dist/core/research.d.ts +66 -0
  176. package/dist/core/research.js +270 -0
  177. package/dist/core/retry.d.ts +60 -0
  178. package/dist/core/retry.js +119 -0
  179. package/dist/core/safe-browsing.d.ts +30 -0
  180. package/dist/core/safe-browsing.js +206 -0
  181. package/dist/core/schema-extraction.d.ts +66 -0
  182. package/dist/core/schema-extraction.js +352 -0
  183. package/dist/core/schema-postprocess.d.ts +32 -0
  184. package/dist/core/schema-postprocess.js +469 -0
  185. package/dist/core/schema-templates.d.ts +19 -0
  186. package/dist/core/schema-templates.js +143 -0
  187. package/dist/core/screenshot.d.ts +224 -0
  188. package/dist/core/screenshot.js +207 -0
  189. package/dist/core/search-engines.d.ts +25 -0
  190. package/dist/core/search-engines.js +182 -0
  191. package/dist/core/search-provider.d.ts +243 -0
  192. package/dist/core/search-provider.js +1629 -0
  193. package/dist/core/searxng-provider.d.ts +35 -0
  194. package/dist/core/searxng-provider.js +105 -0
  195. package/dist/core/selective-evidence.d.ts +151 -0
  196. package/dist/core/selective-evidence.js +389 -0
  197. package/dist/core/site-search.d.ts +44 -0
  198. package/dist/core/site-search.js +252 -0
  199. package/dist/core/sitemap.d.ts +23 -0
  200. package/dist/core/sitemap.js +105 -0
  201. package/dist/core/source-credibility.d.ts +29 -0
  202. package/dist/core/source-credibility.js +584 -0
  203. package/dist/core/source-scoring.d.ts +166 -0
  204. package/dist/core/source-scoring.js +396 -0
  205. package/dist/core/stemmer.d.ts +38 -0
  206. package/dist/core/stemmer.js +509 -0
  207. package/dist/core/strategies.d.ts +104 -0
  208. package/dist/core/strategies.js +1044 -0
  209. package/dist/core/strategy-hooks.d.ts +145 -0
  210. package/dist/core/strategy-hooks.js +74 -0
  211. package/dist/core/structured-extract.d.ts +43 -0
  212. package/dist/core/structured-extract.js +550 -0
  213. package/dist/core/summarize.d.ts +17 -0
  214. package/dist/core/summarize.js +78 -0
  215. package/dist/core/synonyms.d.ts +42 -0
  216. package/dist/core/synonyms.js +184 -0
  217. package/dist/core/system-monitor.d.ts +61 -0
  218. package/dist/core/system-monitor.js +133 -0
  219. package/dist/core/table-format.d.ts +30 -0
  220. package/dist/core/table-format.js +146 -0
  221. package/dist/core/threat-feeds.d.ts +23 -0
  222. package/dist/core/threat-feeds.js +104 -0
  223. package/dist/core/timing.d.ts +21 -0
  224. package/dist/core/timing.js +33 -0
  225. package/dist/core/transcript-export.d.ts +47 -0
  226. package/dist/core/transcript-export.js +107 -0
  227. package/dist/core/user-agents.d.ts +82 -0
  228. package/dist/core/user-agents.js +239 -0
  229. package/dist/core/vertical-search.d.ts +54 -0
  230. package/dist/core/vertical-search.js +158 -0
  231. package/dist/core/watch-manager.d.ts +175 -0
  232. package/dist/core/watch-manager.js +416 -0
  233. package/dist/core/watch.d.ts +101 -0
  234. package/dist/core/watch.js +389 -0
  235. package/dist/core/youtube.d.ts +130 -0
  236. package/dist/core/youtube.js +1175 -0
  237. package/dist/ee/challenge-re-export.d.ts +1 -0
  238. package/dist/ee/challenge-re-export.js +1 -0
  239. package/dist/ee/challenge-solver.d.ts +72 -0
  240. package/dist/ee/challenge-solver.js +720 -0
  241. package/dist/ee/domain-extractors.d.ts +8 -0
  242. package/dist/ee/domain-extractors.js +8 -0
  243. package/dist/ee/domain-intel.d.ts +16 -0
  244. package/dist/ee/domain-intel.js +133 -0
  245. package/dist/ee/extractors/allrecipes.d.ts +2 -0
  246. package/dist/ee/extractors/allrecipes.js +120 -0
  247. package/dist/ee/extractors/amazon.d.ts +2 -0
  248. package/dist/ee/extractors/amazon.js +78 -0
  249. package/dist/ee/extractors/arxiv.d.ts +2 -0
  250. package/dist/ee/extractors/arxiv.js +137 -0
  251. package/dist/ee/extractors/bestbuy.d.ts +2 -0
  252. package/dist/ee/extractors/bestbuy.js +78 -0
  253. package/dist/ee/extractors/carscom.d.ts +2 -0
  254. package/dist/ee/extractors/carscom.js +121 -0
  255. package/dist/ee/extractors/coingecko.d.ts +2 -0
  256. package/dist/ee/extractors/coingecko.js +134 -0
  257. package/dist/ee/extractors/craigslist.d.ts +2 -0
  258. package/dist/ee/extractors/craigslist.js +92 -0
  259. package/dist/ee/extractors/devto.d.ts +2 -0
  260. package/dist/ee/extractors/devto.js +135 -0
  261. package/dist/ee/extractors/ebay.d.ts +2 -0
  262. package/dist/ee/extractors/ebay.js +90 -0
  263. package/dist/ee/extractors/espn.d.ts +2 -0
  264. package/dist/ee/extractors/espn.js +260 -0
  265. package/dist/ee/extractors/etsy.d.ts +2 -0
  266. package/dist/ee/extractors/etsy.js +52 -0
  267. package/dist/ee/extractors/facebook.d.ts +2 -0
  268. package/dist/ee/extractors/facebook.js +46 -0
  269. package/dist/ee/extractors/github.d.ts +2 -0
  270. package/dist/ee/extractors/github.js +196 -0
  271. package/dist/ee/extractors/google-flights.d.ts +2 -0
  272. package/dist/ee/extractors/google-flights.js +176 -0
  273. package/dist/ee/extractors/hackernews.d.ts +2 -0
  274. package/dist/ee/extractors/hackernews.js +147 -0
  275. package/dist/ee/extractors/imdb.d.ts +2 -0
  276. package/dist/ee/extractors/imdb.js +172 -0
  277. package/dist/ee/extractors/index.d.ts +26 -0
  278. package/dist/ee/extractors/index.js +247 -0
  279. package/dist/ee/extractors/instagram.d.ts +2 -0
  280. package/dist/ee/extractors/instagram.js +102 -0
  281. package/dist/ee/extractors/kalshi.d.ts +2 -0
  282. package/dist/ee/extractors/kalshi.js +121 -0
  283. package/dist/ee/extractors/kayak-cars.d.ts +2 -0
  284. package/dist/ee/extractors/kayak-cars.js +270 -0
  285. package/dist/ee/extractors/linkedin.d.ts +2 -0
  286. package/dist/ee/extractors/linkedin.js +113 -0
  287. package/dist/ee/extractors/medium.d.ts +2 -0
  288. package/dist/ee/extractors/medium.js +130 -0
  289. package/dist/ee/extractors/news.d.ts +4 -0
  290. package/dist/ee/extractors/news.js +173 -0
  291. package/dist/ee/extractors/npm.d.ts +2 -0
  292. package/dist/ee/extractors/npm.js +86 -0
  293. package/dist/ee/extractors/pdf.d.ts +2 -0
  294. package/dist/ee/extractors/pdf.js +108 -0
  295. package/dist/ee/extractors/pinterest.d.ts +2 -0
  296. package/dist/ee/extractors/pinterest.js +34 -0
  297. package/dist/ee/extractors/polymarket.d.ts +2 -0
  298. package/dist/ee/extractors/polymarket.js +358 -0
  299. package/dist/ee/extractors/producthunt.d.ts +2 -0
  300. package/dist/ee/extractors/producthunt.js +88 -0
  301. package/dist/ee/extractors/pubmed.d.ts +2 -0
  302. package/dist/ee/extractors/pubmed.js +162 -0
  303. package/dist/ee/extractors/pypi.d.ts +2 -0
  304. package/dist/ee/extractors/pypi.js +80 -0
  305. package/dist/ee/extractors/reddit.d.ts +2 -0
  306. package/dist/ee/extractors/reddit.js +438 -0
  307. package/dist/ee/extractors/redfin.d.ts +2 -0
  308. package/dist/ee/extractors/redfin.js +156 -0
  309. package/dist/ee/extractors/semanticscholar.d.ts +2 -0
  310. package/dist/ee/extractors/semanticscholar.js +131 -0
  311. package/dist/ee/extractors/shared.d.ts +12 -0
  312. package/dist/ee/extractors/shared.js +76 -0
  313. package/dist/ee/extractors/soundcloud.d.ts +2 -0
  314. package/dist/ee/extractors/soundcloud.js +34 -0
  315. package/dist/ee/extractors/sportsbetting.d.ts +2 -0
  316. package/dist/ee/extractors/sportsbetting.js +37 -0
  317. package/dist/ee/extractors/spotify.d.ts +2 -0
  318. package/dist/ee/extractors/spotify.js +34 -0
  319. package/dist/ee/extractors/stackoverflow.d.ts +2 -0
  320. package/dist/ee/extractors/stackoverflow.js +61 -0
  321. package/dist/ee/extractors/substack.d.ts +2 -0
  322. package/dist/ee/extractors/substack.js +115 -0
  323. package/dist/ee/extractors/substackroot.d.ts +2 -0
  324. package/dist/ee/extractors/substackroot.js +46 -0
  325. package/dist/ee/extractors/tiktok.d.ts +2 -0
  326. package/dist/ee/extractors/tiktok.js +29 -0
  327. package/dist/ee/extractors/tradingview.d.ts +2 -0
  328. package/dist/ee/extractors/tradingview.js +182 -0
  329. package/dist/ee/extractors/twitch.d.ts +2 -0
  330. package/dist/ee/extractors/twitch.js +36 -0
  331. package/dist/ee/extractors/twitter.d.ts +2 -0
  332. package/dist/ee/extractors/twitter.js +327 -0
  333. package/dist/ee/extractors/types.d.ts +14 -0
  334. package/dist/ee/extractors/types.js +1 -0
  335. package/dist/ee/extractors/walmart.d.ts +2 -0
  336. package/dist/ee/extractors/walmart.js +50 -0
  337. package/dist/ee/extractors/weather.d.ts +2 -0
  338. package/dist/ee/extractors/weather.js +133 -0
  339. package/dist/ee/extractors/wikipedia.d.ts +4 -0
  340. package/dist/ee/extractors/wikipedia.js +235 -0
  341. package/dist/ee/extractors/yelp.d.ts +2 -0
  342. package/dist/ee/extractors/yelp.js +216 -0
  343. package/dist/ee/extractors/youtube.d.ts +2 -0
  344. package/dist/ee/extractors/youtube.js +189 -0
  345. package/dist/ee/extractors/zillow.d.ts +54 -0
  346. package/dist/ee/extractors/zillow.js +247 -0
  347. package/dist/ee/extractors-re-export.d.ts +1 -0
  348. package/dist/ee/extractors-re-export.js +1 -0
  349. package/dist/ee/premium-hooks.d.ts +20 -0
  350. package/dist/ee/premium-hooks.js +50 -0
  351. package/dist/ee/spa-detection.d.ts +2 -0
  352. package/dist/ee/spa-detection.js +2 -0
  353. package/dist/ee/stability.d.ts +4 -0
  354. package/dist/ee/stability.js +29 -0
  355. package/dist/ee/swr-cache.d.ts +14 -0
  356. package/dist/ee/swr-cache.js +34 -0
  357. package/dist/index.d.ts +143 -0
  358. package/dist/index.js +291 -0
  359. package/dist/integrations/index.d.ts +2 -0
  360. package/dist/integrations/index.js +2 -0
  361. package/dist/integrations/langchain.d.ts +64 -0
  362. package/dist/integrations/langchain.js +115 -0
  363. package/dist/integrations/llamaindex.d.ts +50 -0
  364. package/dist/integrations/llamaindex.js +91 -0
  365. package/dist/mcp/handlers/act.d.ts +5 -0
  366. package/dist/mcp/handlers/act.js +34 -0
  367. package/dist/mcp/handlers/definitions.d.ts +6 -0
  368. package/dist/mcp/handlers/definitions.js +395 -0
  369. package/dist/mcp/handlers/extract.d.ts +7 -0
  370. package/dist/mcp/handlers/extract.js +135 -0
  371. package/dist/mcp/handlers/fetch.d.ts +6 -0
  372. package/dist/mcp/handlers/fetch.js +98 -0
  373. package/dist/mcp/handlers/find.d.ts +5 -0
  374. package/dist/mcp/handlers/find.js +137 -0
  375. package/dist/mcp/handlers/index.d.ts +13 -0
  376. package/dist/mcp/handlers/index.js +63 -0
  377. package/dist/mcp/handlers/legacy.d.ts +25 -0
  378. package/dist/mcp/handlers/legacy.js +450 -0
  379. package/dist/mcp/handlers/meta.d.ts +6 -0
  380. package/dist/mcp/handlers/meta.js +40 -0
  381. package/dist/mcp/handlers/monitor.d.ts +5 -0
  382. package/dist/mcp/handlers/monitor.js +41 -0
  383. package/dist/mcp/handlers/observe.d.ts +8 -0
  384. package/dist/mcp/handlers/observe.js +37 -0
  385. package/dist/mcp/handlers/read.d.ts +6 -0
  386. package/dist/mcp/handlers/read.js +78 -0
  387. package/dist/mcp/handlers/see.d.ts +5 -0
  388. package/dist/mcp/handlers/see.js +75 -0
  389. package/dist/mcp/handlers/types.d.ts +29 -0
  390. package/dist/mcp/handlers/types.js +28 -0
  391. package/dist/mcp/server.d.ts +7 -0
  392. package/dist/mcp/server.js +108 -0
  393. package/dist/mcp/smart-router.d.ts +23 -0
  394. package/dist/mcp/smart-router.js +178 -0
  395. package/dist/server/app.d.ts +14 -0
  396. package/dist/server/app.js +632 -0
  397. package/dist/server/auth-store.d.ts +28 -0
  398. package/dist/server/auth-store.js +88 -0
  399. package/dist/server/bull-queues.d.ts +60 -0
  400. package/dist/server/bull-queues.js +90 -0
  401. package/dist/server/email-service.d.ts +55 -0
  402. package/dist/server/email-service.js +291 -0
  403. package/dist/server/job-queue.d.ts +100 -0
  404. package/dist/server/job-queue.js +145 -0
  405. package/dist/server/logger.d.ts +10 -0
  406. package/dist/server/logger.js +37 -0
  407. package/dist/server/middleware/audit-log.d.ts +14 -0
  408. package/dist/server/middleware/audit-log.js +73 -0
  409. package/dist/server/middleware/auth.d.ts +35 -0
  410. package/dist/server/middleware/auth.js +225 -0
  411. package/dist/server/middleware/rate-limit.d.ts +50 -0
  412. package/dist/server/middleware/rate-limit.js +270 -0
  413. package/dist/server/middleware/scope-guard.d.ts +25 -0
  414. package/dist/server/middleware/scope-guard.js +45 -0
  415. package/dist/server/middleware/url-validator.d.ts +15 -0
  416. package/dist/server/middleware/url-validator.js +201 -0
  417. package/dist/server/openapi.yaml +6418 -0
  418. package/dist/server/pg-auth-store.d.ts +146 -0
  419. package/dist/server/pg-auth-store.js +576 -0
  420. package/dist/server/pg-job-queue.d.ts +59 -0
  421. package/dist/server/pg-job-queue.js +375 -0
  422. package/dist/server/routes/activity.d.ts +6 -0
  423. package/dist/server/routes/activity.js +79 -0
  424. package/dist/server/routes/admin-active.d.ts +7 -0
  425. package/dist/server/routes/admin-active.js +120 -0
  426. package/dist/server/routes/admin-stats.d.ts +7 -0
  427. package/dist/server/routes/admin-stats.js +176 -0
  428. package/dist/server/routes/agent.d.ts +24 -0
  429. package/dist/server/routes/agent.js +480 -0
  430. package/dist/server/routes/answer.d.ts +5 -0
  431. package/dist/server/routes/answer.js +125 -0
  432. package/dist/server/routes/ask.d.ts +28 -0
  433. package/dist/server/routes/ask.js +295 -0
  434. package/dist/server/routes/batch.d.ts +6 -0
  435. package/dist/server/routes/batch.js +493 -0
  436. package/dist/server/routes/cache-warm.d.ts +25 -0
  437. package/dist/server/routes/cache-warm.js +212 -0
  438. package/dist/server/routes/cli-usage.d.ts +6 -0
  439. package/dist/server/routes/cli-usage.js +127 -0
  440. package/dist/server/routes/compat.d.ts +23 -0
  441. package/dist/server/routes/compat.js +652 -0
  442. package/dist/server/routes/crawl.d.ts +13 -0
  443. package/dist/server/routes/crawl.js +287 -0
  444. package/dist/server/routes/deep-fetch.d.ts +8 -0
  445. package/dist/server/routes/deep-fetch.js +57 -0
  446. package/dist/server/routes/deep-research.d.ts +11 -0
  447. package/dist/server/routes/deep-research.js +232 -0
  448. package/dist/server/routes/demo.d.ts +24 -0
  449. package/dist/server/routes/demo.js +517 -0
  450. package/dist/server/routes/do.d.ts +8 -0
  451. package/dist/server/routes/do.js +72 -0
  452. package/dist/server/routes/extract.d.ts +14 -0
  453. package/dist/server/routes/extract.js +325 -0
  454. package/dist/server/routes/feed.d.ts +15 -0
  455. package/dist/server/routes/feed.js +311 -0
  456. package/dist/server/routes/fetch-queue.d.ts +13 -0
  457. package/dist/server/routes/fetch-queue.js +357 -0
  458. package/dist/server/routes/fetch.d.ts +7 -0
  459. package/dist/server/routes/fetch.js +1274 -0
  460. package/dist/server/routes/go.d.ts +14 -0
  461. package/dist/server/routes/go.js +81 -0
  462. package/dist/server/routes/health.d.ts +11 -0
  463. package/dist/server/routes/health.js +141 -0
  464. package/dist/server/routes/jobs.d.ts +7 -0
  465. package/dist/server/routes/jobs.js +574 -0
  466. package/dist/server/routes/map.d.ts +11 -0
  467. package/dist/server/routes/map.js +116 -0
  468. package/dist/server/routes/mcp.d.ts +14 -0
  469. package/dist/server/routes/mcp.js +197 -0
  470. package/dist/server/routes/metrics.d.ts +37 -0
  471. package/dist/server/routes/metrics.js +149 -0
  472. package/dist/server/routes/oauth.d.ts +9 -0
  473. package/dist/server/routes/oauth.js +396 -0
  474. package/dist/server/routes/playground.d.ts +17 -0
  475. package/dist/server/routes/playground.js +283 -0
  476. package/dist/server/routes/reader.d.ts +18 -0
  477. package/dist/server/routes/reader.js +192 -0
  478. package/dist/server/routes/research.d.ts +14 -0
  479. package/dist/server/routes/research.js +482 -0
  480. package/dist/server/routes/screenshot.d.ts +22 -0
  481. package/dist/server/routes/screenshot.js +820 -0
  482. package/dist/server/routes/search.d.ts +6 -0
  483. package/dist/server/routes/search.js +874 -0
  484. package/dist/server/routes/session.d.ts +17 -0
  485. package/dist/server/routes/session.js +548 -0
  486. package/dist/server/routes/share.d.ts +18 -0
  487. package/dist/server/routes/share.js +462 -0
  488. package/dist/server/routes/smart-search/handlers/cars.d.ts +2 -0
  489. package/dist/server/routes/smart-search/handlers/cars.js +102 -0
  490. package/dist/server/routes/smart-search/handlers/flights.d.ts +2 -0
  491. package/dist/server/routes/smart-search/handlers/flights.js +72 -0
  492. package/dist/server/routes/smart-search/handlers/general.d.ts +13 -0
  493. package/dist/server/routes/smart-search/handlers/general.js +717 -0
  494. package/dist/server/routes/smart-search/handlers/hotels.d.ts +2 -0
  495. package/dist/server/routes/smart-search/handlers/hotels.js +88 -0
  496. package/dist/server/routes/smart-search/handlers/products.d.ts +2 -0
  497. package/dist/server/routes/smart-search/handlers/products.js +1309 -0
  498. package/dist/server/routes/smart-search/handlers/rental.d.ts +2 -0
  499. package/dist/server/routes/smart-search/handlers/rental.js +154 -0
  500. package/dist/server/routes/smart-search/handlers/restaurants.d.ts +2 -0
  501. package/dist/server/routes/smart-search/handlers/restaurants.js +225 -0
  502. package/dist/server/routes/smart-search/handlers/transit-verdict.d.ts +41 -0
  503. package/dist/server/routes/smart-search/handlers/transit-verdict.js +224 -0
  504. package/dist/server/routes/smart-search/index.d.ts +19 -0
  505. package/dist/server/routes/smart-search/index.js +546 -0
  506. package/dist/server/routes/smart-search/intent.d.ts +3 -0
  507. package/dist/server/routes/smart-search/intent.js +264 -0
  508. package/dist/server/routes/smart-search/llm.d.ts +16 -0
  509. package/dist/server/routes/smart-search/llm.js +70 -0
  510. package/dist/server/routes/smart-search/sources/reddit.d.ts +18 -0
  511. package/dist/server/routes/smart-search/sources/reddit.js +34 -0
  512. package/dist/server/routes/smart-search/sources/yelp.d.ts +25 -0
  513. package/dist/server/routes/smart-search/sources/yelp.js +171 -0
  514. package/dist/server/routes/smart-search/sources/youtube.d.ts +8 -0
  515. package/dist/server/routes/smart-search/sources/youtube.js +9 -0
  516. package/dist/server/routes/smart-search/types.d.ts +81 -0
  517. package/dist/server/routes/smart-search/types.js +1 -0
  518. package/dist/server/routes/smart-search/utils.d.ts +20 -0
  519. package/dist/server/routes/smart-search/utils.js +146 -0
  520. package/dist/server/routes/stats.d.ts +6 -0
  521. package/dist/server/routes/stats.js +71 -0
  522. package/dist/server/routes/stripe.d.ts +15 -0
  523. package/dist/server/routes/stripe.js +296 -0
  524. package/dist/server/routes/transcript-export.d.ts +10 -0
  525. package/dist/server/routes/transcript-export.js +178 -0
  526. package/dist/server/routes/usage.d.ts +9 -0
  527. package/dist/server/routes/usage.js +279 -0
  528. package/dist/server/routes/users.d.ts +8 -0
  529. package/dist/server/routes/users.js +1867 -0
  530. package/dist/server/routes/watch.d.ts +15 -0
  531. package/dist/server/routes/watch.js +309 -0
  532. package/dist/server/routes/webhooks.d.ts +26 -0
  533. package/dist/server/routes/webhooks.js +170 -0
  534. package/dist/server/routes/youtube.d.ts +6 -0
  535. package/dist/server/routes/youtube.js +130 -0
  536. package/dist/server/sentry.d.ts +14 -0
  537. package/dist/server/sentry.js +104 -0
  538. package/dist/server/types.d.ts +15 -0
  539. package/dist/server/types.js +7 -0
  540. package/dist/server/utils/response.d.ts +44 -0
  541. package/dist/server/utils/response.js +69 -0
  542. package/dist/server/utils/sse.d.ts +22 -0
  543. package/dist/server/utils/sse.js +38 -0
  544. package/dist/types.d.ts +552 -0
  545. package/dist/types.js +39 -0
  546. package/llms.txt +105 -0
  547. 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>;