@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,395 @@
1
+ /**
2
+ * /answer core implementation
3
+ *
4
+ * Flow:
5
+ * - search the web
6
+ * - fetch top sources via WebPeel
7
+ * - call an LLM (BYOK) to generate a cited answer
8
+ */
9
+ import { peel } from '../index.js';
10
+ import { getSearchProvider } from './search-provider.js';
11
+ function defaultModelForProvider(provider) {
12
+ switch (provider) {
13
+ case 'openai':
14
+ return 'gpt-4o-mini';
15
+ case 'anthropic':
16
+ return 'claude-3-5-sonnet-latest';
17
+ case 'google':
18
+ return 'gemini-1.5-flash';
19
+ }
20
+ }
21
+ function clamp(n, min, max) {
22
+ return Math.min(Math.max(n, min), max);
23
+ }
24
+ function truncateChars(text, maxChars) {
25
+ if (text.length <= maxChars)
26
+ return text;
27
+ return text.slice(0, maxChars) + '\n\n[Truncated]';
28
+ }
29
+ async function fetchSources(results, maxSources, signal) {
30
+ const chosen = results.slice(0, maxSources);
31
+ // Fetch in parallel, but keep it modest to avoid resource spikes.
32
+ const concurrency = 3;
33
+ const out = [];
34
+ for (let i = 0; i < chosen.length; i += concurrency) {
35
+ const batch = chosen.slice(i, i + concurrency);
36
+ const settled = await Promise.allSettled(batch.map(async (r) => {
37
+ const timeoutMs = 30_000;
38
+ if (signal?.aborted) {
39
+ throw new Error('Aborted');
40
+ }
41
+ const pr = await peel(r.url, {
42
+ format: 'markdown',
43
+ maxTokens: 1800,
44
+ timeout: timeoutMs,
45
+ // Keep it cheap/fast by default; users can always refetch with render=true.
46
+ render: false,
47
+ });
48
+ return { result: r, content: pr.content };
49
+ }));
50
+ for (let j = 0; j < settled.length; j++) {
51
+ const s = settled[j];
52
+ if (s.status === 'fulfilled') {
53
+ out.push({
54
+ result: s.value.result,
55
+ content: s.value.content,
56
+ });
57
+ }
58
+ else {
59
+ out.push({
60
+ result: batch[j],
61
+ content: `[Failed to fetch: ${s.reason instanceof Error ? s.reason.message : 'Unknown error'}]`,
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ function buildCitedContext(sources) {
69
+ const parts = [];
70
+ sources.forEach((s, i) => {
71
+ const n = i + 1;
72
+ const title = s.result.title || '(untitled)';
73
+ const url = s.result.url;
74
+ const snippet = s.result.snippet || '';
75
+ // Sanitize untrusted web content before passing to LLM
76
+ const rawContent = truncateChars(s.content || '', 20_000);
77
+ const sanitized = sanitizeForLLM(rawContent);
78
+ if (sanitized.injectionDetected) {
79
+ console.log(`[webpeel] [prompt-guard] Injection patterns detected in source [${n}] (${url}): ${sanitized.detectedPatterns.join(', ')}`);
80
+ }
81
+ parts.push(`SOURCE [${n}]\nTitle: ${title}\nURL: ${url}\nSnippet: ${truncateChars(snippet, 800)}\n\nContent (markdown):\n${sanitized.content}`);
82
+ });
83
+ return parts.join('\n\n---\n\n');
84
+ }
85
+ async function readTextStream(body, onText, signal) {
86
+ const reader = body.getReader();
87
+ const decoder = new TextDecoder();
88
+ let buffer = '';
89
+ try {
90
+ while (true) {
91
+ if (signal?.aborted)
92
+ throw new Error('Aborted');
93
+ const { done, value } = await reader.read();
94
+ if (done)
95
+ break;
96
+ buffer += decoder.decode(value, { stream: true });
97
+ // Process complete SSE events separated by blank lines
98
+ while (true) {
99
+ const idx = buffer.indexOf('\n\n');
100
+ if (idx === -1)
101
+ break;
102
+ const event = buffer.slice(0, idx);
103
+ buffer = buffer.slice(idx + 2);
104
+ const lines = event.split('\n');
105
+ for (const line of lines) {
106
+ const trimmed = line.trim();
107
+ if (!trimmed.startsWith('data:'))
108
+ continue;
109
+ const data = trimmed.slice(5).trim();
110
+ onText(data);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ finally {
116
+ reader.releaseLock();
117
+ }
118
+ }
119
+ async function callOpenAI(params) {
120
+ const { apiKey, model, messages, stream, onChunk, signal } = params;
121
+ const resp = await fetch('https://api.openai.com/v1/chat/completions', {
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json',
125
+ 'Authorization': `Bearer ${apiKey}`,
126
+ },
127
+ body: JSON.stringify({
128
+ model,
129
+ messages,
130
+ temperature: 0.2,
131
+ stream,
132
+ }),
133
+ signal,
134
+ });
135
+ if (!resp.ok) {
136
+ const text = await resp.text().catch(() => '');
137
+ throw new Error(`OpenAI API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
138
+ }
139
+ if (!stream) {
140
+ const json = await resp.json();
141
+ const text = String(json?.choices?.[0]?.message?.content || '').trim();
142
+ const usage = {
143
+ input: Number(json?.usage?.prompt_tokens || 0),
144
+ output: Number(json?.usage?.completion_tokens || 0),
145
+ };
146
+ return { text, usage };
147
+ }
148
+ if (!resp.body)
149
+ throw new Error('OpenAI stream error: missing body');
150
+ let out = '';
151
+ let usage = { input: 0, output: 0 };
152
+ await readTextStream(resp.body, (data) => {
153
+ if (data === '[DONE]')
154
+ return;
155
+ let obj;
156
+ try {
157
+ obj = JSON.parse(data);
158
+ }
159
+ catch {
160
+ return;
161
+ }
162
+ const delta = obj?.choices?.[0]?.delta?.content;
163
+ if (typeof delta === 'string' && delta.length > 0) {
164
+ out += delta;
165
+ onChunk?.(delta);
166
+ }
167
+ if (obj?.usage) {
168
+ usage = {
169
+ input: Number(obj.usage.prompt_tokens || usage.input),
170
+ output: Number(obj.usage.completion_tokens || usage.output),
171
+ };
172
+ }
173
+ }, signal);
174
+ return { text: out.trim(), usage };
175
+ }
176
+ async function callAnthropic(params) {
177
+ const { apiKey, model, system, user, stream, onChunk, signal } = params;
178
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ 'x-api-key': apiKey,
183
+ 'anthropic-version': '2023-06-01',
184
+ ...(stream ? { 'Accept': 'text/event-stream' } : {}),
185
+ },
186
+ body: JSON.stringify({
187
+ model,
188
+ system,
189
+ messages: [{ role: 'user', content: user }],
190
+ max_tokens: 4096,
191
+ temperature: 0.2,
192
+ stream,
193
+ }),
194
+ signal,
195
+ });
196
+ if (!resp.ok) {
197
+ const text = await resp.text().catch(() => '');
198
+ throw new Error(`Anthropic API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
199
+ }
200
+ if (!stream) {
201
+ const json = await resp.json();
202
+ const blocks = Array.isArray(json?.content) ? json.content : [];
203
+ const text = blocks.map((b) => (typeof b?.text === 'string' ? b.text : '')).join('').trim();
204
+ const usage = {
205
+ input: Number(json?.usage?.input_tokens || 0),
206
+ output: Number(json?.usage?.output_tokens || 0),
207
+ };
208
+ return { text, usage };
209
+ }
210
+ if (!resp.body)
211
+ throw new Error('Anthropic stream error: missing body');
212
+ let out = '';
213
+ let usage = { input: 0, output: 0 };
214
+ await readTextStream(resp.body, (data) => {
215
+ let obj;
216
+ try {
217
+ obj = JSON.parse(data);
218
+ }
219
+ catch {
220
+ return;
221
+ }
222
+ // Streaming event types: content_block_delta, message_delta, message_stop, etc.
223
+ if (obj?.type === 'content_block_delta') {
224
+ const delta = obj?.delta?.text;
225
+ if (typeof delta === 'string' && delta.length > 0) {
226
+ out += delta;
227
+ onChunk?.(delta);
228
+ }
229
+ }
230
+ if (obj?.type === 'message_delta' && obj?.usage) {
231
+ usage = {
232
+ input: Number(obj.usage.input_tokens || usage.input),
233
+ output: Number(obj.usage.output_tokens || usage.output),
234
+ };
235
+ }
236
+ if (obj?.type === 'message_stop' && obj?.message?.usage) {
237
+ usage = {
238
+ input: Number(obj.message.usage.input_tokens || usage.input),
239
+ output: Number(obj.message.usage.output_tokens || usage.output),
240
+ };
241
+ }
242
+ }, signal);
243
+ return { text: out.trim(), usage };
244
+ }
245
+ async function callGoogle(params) {
246
+ const { apiKey, model, system, user, signal } = params;
247
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
248
+ const resp = await fetch(url, {
249
+ method: 'POST',
250
+ headers: {
251
+ 'Content-Type': 'application/json',
252
+ },
253
+ body: JSON.stringify({
254
+ contents: [
255
+ {
256
+ role: 'user',
257
+ parts: [{ text: `${system}\n\n${user}` }],
258
+ },
259
+ ],
260
+ generationConfig: {
261
+ temperature: 0.2,
262
+ },
263
+ }),
264
+ signal,
265
+ });
266
+ if (!resp.ok) {
267
+ const text = await resp.text().catch(() => '');
268
+ throw new Error(`Google API error: HTTP ${resp.status}${text ? ` - ${text}` : ''}`);
269
+ }
270
+ const json = await resp.json();
271
+ const parts = json?.candidates?.[0]?.content?.parts;
272
+ const text = Array.isArray(parts)
273
+ ? parts.map((p) => (typeof p?.text === 'string' ? p.text : '')).join('')
274
+ : '';
275
+ const usage = {
276
+ input: Number(json?.usageMetadata?.promptTokenCount || 0),
277
+ output: Number(json?.usageMetadata?.candidatesTokenCount || 0),
278
+ };
279
+ return { text: String(text || '').trim(), usage };
280
+ }
281
+ import { sanitizeForLLM, hardenSystemPrompt, validateOutput } from './prompt-guard.js';
282
+ const BASE_SYSTEM_PROMPT = [
283
+ 'You are a helpful assistant that answers questions using ONLY the provided sources.',
284
+ 'You must cite sources using bracketed numbers like [1], [2], etc. corresponding to the sources list.',
285
+ 'If the sources do not contain the answer, say you do not know.',
286
+ 'Do not fabricate URLs or citations.',
287
+ ].join('\n');
288
+ function systemPrompt() {
289
+ return hardenSystemPrompt(BASE_SYSTEM_PROMPT);
290
+ }
291
+ export async function answerQuestion(req) {
292
+ const question = (req.question || '').trim();
293
+ if (!question)
294
+ throw new Error('Missing or invalid "question"');
295
+ if (question.length > 2000)
296
+ throw new Error('Question too long (max 2000 characters)');
297
+ const searchProvider = req.searchProvider || 'duckduckgo';
298
+ const maxSources = clamp(req.maxSources ?? 5, 1, 10);
299
+ const llmProvider = req.llmProvider;
300
+ const llmApiKey = (req.llmApiKey || '').trim();
301
+ if (!llmApiKey)
302
+ throw new Error('Missing or invalid "llmApiKey" (BYOK required)');
303
+ const llmModel = (req.llmModel || '').trim() || defaultModelForProvider(llmProvider);
304
+ // 1) Search
305
+ const provider = getSearchProvider(searchProvider);
306
+ const searchResults = await provider.searchWeb(question, {
307
+ count: maxSources,
308
+ apiKey: req.searchApiKey,
309
+ signal: req.signal,
310
+ });
311
+ const citations = searchResults.slice(0, maxSources).map((r) => ({
312
+ title: r.title,
313
+ url: r.url,
314
+ snippet: r.snippet,
315
+ }));
316
+ // 2) Fetch sources
317
+ const fetched = await fetchSources(searchResults, maxSources, req.signal);
318
+ const context = buildCitedContext(fetched);
319
+ // 3) LLM
320
+ const sys = systemPrompt();
321
+ const user = `Question:\n${question}\n\nSources:\n\n${context}\n\nWrite the best possible answer. Remember to cite sources like [1], [2].`;
322
+ const stream = req.stream === true;
323
+ let answer = '';
324
+ let tokensUsed = { input: 0, output: 0 };
325
+ if (llmProvider === 'openai') {
326
+ const messages = [
327
+ { role: 'system', content: sys },
328
+ { role: 'user', content: user },
329
+ ];
330
+ const r = await callOpenAI({
331
+ apiKey: llmApiKey,
332
+ model: llmModel,
333
+ messages,
334
+ stream,
335
+ onChunk: req.onChunk,
336
+ signal: req.signal,
337
+ });
338
+ answer = r.text;
339
+ tokensUsed = r.usage;
340
+ }
341
+ else if (llmProvider === 'anthropic') {
342
+ const r = await callAnthropic({
343
+ apiKey: llmApiKey,
344
+ model: llmModel,
345
+ system: sys,
346
+ user,
347
+ stream,
348
+ onChunk: req.onChunk,
349
+ signal: req.signal,
350
+ });
351
+ answer = r.text;
352
+ tokensUsed = r.usage;
353
+ }
354
+ else if (llmProvider === 'google') {
355
+ // Google streaming is not implemented here; when stream=true, the caller can
356
+ // still stream the final text in chunks.
357
+ const r = await callGoogle({
358
+ apiKey: llmApiKey,
359
+ model: llmModel,
360
+ system: sys,
361
+ user,
362
+ signal: req.signal,
363
+ });
364
+ answer = r.text;
365
+ tokensUsed = r.usage;
366
+ if (stream && req.onChunk) {
367
+ // Emit reasonable chunks so SSE clients can start rendering.
368
+ const chunkSize = 120;
369
+ for (let i = 0; i < answer.length; i += chunkSize) {
370
+ req.onChunk(answer.slice(i, i + chunkSize));
371
+ }
372
+ }
373
+ }
374
+ else {
375
+ throw new Error(`Unsupported llmProvider: ${llmProvider}`);
376
+ }
377
+ // Validate output for signs of successful injection
378
+ const outputCheck = validateOutput(answer, [
379
+ 'cite sources using bracketed',
380
+ 'do not fabricate urls',
381
+ 'security rules',
382
+ ]);
383
+ if (!outputCheck.clean) {
384
+ console.log(`[webpeel] [prompt-guard] Output validation issues: ${outputCheck.issues.join(', ')}`);
385
+ // Don't block the response — log for monitoring. In future, could redact or retry.
386
+ }
387
+ return {
388
+ answer,
389
+ citations,
390
+ searchProvider,
391
+ llmProvider,
392
+ llmModel,
393
+ tokensUsed,
394
+ };
395
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Application Tracker — persists job application records to disk.
3
+ *
4
+ * Stored at: ~/.webpeel/applications.json
5
+ *
6
+ * Features:
7
+ * - Add / update application records
8
+ * - Duplicate-URL detection
9
+ * - Filter by platform, status, or since-date
10
+ * - Quick stats summary (total, by-platform, by-status, this-week, today)
11
+ */
12
+ export type ApplicationStatus = 'applied' | 'saved' | 'failed' | 'skipped' | 'interview' | 'rejected' | 'offer';
13
+ export interface ApplicationRecord {
14
+ /** Unique ID (UUID v4) */
15
+ id: string;
16
+ /** Job URL (canonical, used for duplicate detection) */
17
+ url: string;
18
+ /** Job title */
19
+ title: string;
20
+ /** Company name */
21
+ company: string;
22
+ /** Location string */
23
+ location?: string;
24
+ /** Salary / compensation info */
25
+ salary?: string;
26
+ /** Platform identifier: linkedin, indeed, upwork, etc. */
27
+ platform: string;
28
+ /** Current application status */
29
+ status: ApplicationStatus;
30
+ /** ISO-8601 timestamp of the initial application */
31
+ appliedAt: string;
32
+ /** Cover letter or proposal text used */
33
+ coverLetter?: string;
34
+ /** Answers given to screening questions */
35
+ screeningAnswers?: Record<string, string>;
36
+ /** Error message if the application failed */
37
+ error?: string;
38
+ /** Free-form notes */
39
+ notes?: string;
40
+ }
41
+ export interface ApplicationFilter {
42
+ platform?: string;
43
+ status?: string;
44
+ /** ISO date string — only return records on or after this date */
45
+ since?: string;
46
+ }
47
+ export interface ApplicationStats {
48
+ total: number;
49
+ byPlatform: Record<string, number>;
50
+ byStatus: Record<string, number>;
51
+ thisWeek: number;
52
+ today: number;
53
+ }
54
+ export declare class ApplicationTracker {
55
+ private records;
56
+ constructor();
57
+ /**
58
+ * Add a new application record.
59
+ * Assigns a random UUID and returns the completed record.
60
+ */
61
+ add(record: Omit<ApplicationRecord, 'id'>): ApplicationRecord;
62
+ /**
63
+ * Update the status (and optionally notes) of an existing record.
64
+ * Throws if the ID is not found.
65
+ */
66
+ updateStatus(id: string, status: ApplicationStatus, notes?: string): void;
67
+ /**
68
+ * Return true if there is already an 'applied' record for this URL.
69
+ * Normalises the URL by stripping trailing slashes and query strings that
70
+ * are tracking parameters (utm_*, ref, etc.) before comparison.
71
+ */
72
+ hasApplied(url: string): boolean;
73
+ /**
74
+ * Return records, optionally filtered by platform, status, and/or since-date.
75
+ * Results are sorted newest-first.
76
+ */
77
+ list(filter?: ApplicationFilter): ApplicationRecord[];
78
+ /**
79
+ * Return an aggregate stats summary.
80
+ */
81
+ stats(): ApplicationStats;
82
+ private loadRecords;
83
+ private save;
84
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Application Tracker — persists job application records to disk.
3
+ *
4
+ * Stored at: ~/.webpeel/applications.json
5
+ *
6
+ * Features:
7
+ * - Add / update application records
8
+ * - Duplicate-URL detection
9
+ * - Filter by platform, status, or since-date
10
+ * - Quick stats summary (total, by-platform, by-status, this-week, today)
11
+ */
12
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
13
+ import { homedir } from 'os';
14
+ import { join } from 'path';
15
+ import { randomUUID } from 'crypto';
16
+ // ── Storage ────────────────────────────────────────────────────────────
17
+ const WEBPEEL_DIR = join(homedir(), '.webpeel');
18
+ const APPLICATIONS_PATH = join(WEBPEEL_DIR, 'applications.json');
19
+ // ── ApplicationTracker class ───────────────────────────────────────────
20
+ export class ApplicationTracker {
21
+ records;
22
+ constructor() {
23
+ this.records = this.loadRecords();
24
+ }
25
+ // ── Public API ───────────────────────────────────────────────────────
26
+ /**
27
+ * Add a new application record.
28
+ * Assigns a random UUID and returns the completed record.
29
+ */
30
+ add(record) {
31
+ const full = {
32
+ id: randomUUID(),
33
+ ...record,
34
+ };
35
+ this.records.push(full);
36
+ this.save();
37
+ return full;
38
+ }
39
+ /**
40
+ * Update the status (and optionally notes) of an existing record.
41
+ * Throws if the ID is not found.
42
+ */
43
+ updateStatus(id, status, notes) {
44
+ const record = this.records.find((r) => r.id === id);
45
+ if (!record) {
46
+ throw new Error(`Application record not found: ${id}`);
47
+ }
48
+ record.status = status;
49
+ if (notes !== undefined) {
50
+ record.notes = notes;
51
+ }
52
+ this.save();
53
+ }
54
+ /**
55
+ * Return true if there is already an 'applied' record for this URL.
56
+ * Normalises the URL by stripping trailing slashes and query strings that
57
+ * are tracking parameters (utm_*, ref, etc.) before comparison.
58
+ */
59
+ hasApplied(url) {
60
+ const norm = normaliseUrl(url);
61
+ return this.records.some((r) => normaliseUrl(r.url) === norm && r.status === 'applied');
62
+ }
63
+ /**
64
+ * Return records, optionally filtered by platform, status, and/or since-date.
65
+ * Results are sorted newest-first.
66
+ */
67
+ list(filter) {
68
+ let results = [...this.records];
69
+ if (filter?.platform) {
70
+ const p = filter.platform.toLowerCase();
71
+ results = results.filter((r) => r.platform.toLowerCase() === p);
72
+ }
73
+ if (filter?.status) {
74
+ const s = filter.status.toLowerCase();
75
+ results = results.filter((r) => r.status.toLowerCase() === s);
76
+ }
77
+ if (filter?.since) {
78
+ const sinceTs = Date.parse(filter.since);
79
+ if (!isNaN(sinceTs)) {
80
+ results = results.filter((r) => Date.parse(r.appliedAt) >= sinceTs);
81
+ }
82
+ }
83
+ // Newest first
84
+ results.sort((a, b) => Date.parse(b.appliedAt) - Date.parse(a.appliedAt));
85
+ return results;
86
+ }
87
+ /**
88
+ * Return an aggregate stats summary.
89
+ */
90
+ stats() {
91
+ const now = new Date();
92
+ const todayStr = toDateString(now);
93
+ const weekAgo = new Date(now);
94
+ weekAgo.setDate(weekAgo.getDate() - 7);
95
+ const weekAgoTs = weekAgo.getTime();
96
+ const byPlatform = {};
97
+ const byStatus = {};
98
+ let thisWeek = 0;
99
+ let today = 0;
100
+ for (const r of this.records) {
101
+ // Platform counts
102
+ byPlatform[r.platform] = (byPlatform[r.platform] ?? 0) + 1;
103
+ // Status counts
104
+ byStatus[r.status] = (byStatus[r.status] ?? 0) + 1;
105
+ // Time-based counts
106
+ const appliedTs = Date.parse(r.appliedAt);
107
+ if (!isNaN(appliedTs)) {
108
+ if (appliedTs >= weekAgoTs)
109
+ thisWeek++;
110
+ if (toDateString(new Date(appliedTs)) === todayStr)
111
+ today++;
112
+ }
113
+ }
114
+ return {
115
+ total: this.records.length,
116
+ byPlatform,
117
+ byStatus,
118
+ thisWeek,
119
+ today,
120
+ };
121
+ }
122
+ // ── Private helpers ──────────────────────────────────────────────────
123
+ loadRecords() {
124
+ try {
125
+ if (existsSync(APPLICATIONS_PATH)) {
126
+ const raw = readFileSync(APPLICATIONS_PATH, 'utf-8');
127
+ const parsed = JSON.parse(raw);
128
+ if (Array.isArray(parsed))
129
+ return parsed;
130
+ }
131
+ }
132
+ catch (e) {
133
+ if (process.env.DEBUG)
134
+ console.debug('[webpeel]', 'records load failed:', e instanceof Error ? e.message : e);
135
+ }
136
+ return [];
137
+ }
138
+ save() {
139
+ try {
140
+ mkdirSync(WEBPEEL_DIR, { recursive: true });
141
+ writeFileSync(APPLICATIONS_PATH, JSON.stringify(this.records, null, 2), 'utf-8');
142
+ }
143
+ catch (err) {
144
+ console.error('[application-tracker] Failed to save records:', err);
145
+ }
146
+ }
147
+ }
148
+ // ── Utility ────────────────────────────────────────────────────────────
149
+ /** Format a Date as YYYY-MM-DD in local time. */
150
+ function toDateString(d) {
151
+ const y = d.getFullYear();
152
+ const m = String(d.getMonth() + 1).padStart(2, '0');
153
+ const day = String(d.getDate()).padStart(2, '0');
154
+ return `${y}-${m}-${day}`;
155
+ }
156
+ /** Tracking parameters to strip before URL comparison. */
157
+ const TRACKING_PARAMS = new Set([
158
+ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
159
+ 'ref', 'referer', 'referrer', 'source', 'trk', 'trackingId',
160
+ ]);
161
+ /**
162
+ * Strip trailing slashes, fragments, and common tracking query parameters
163
+ * from a URL for stable duplicate detection.
164
+ */
165
+ function normaliseUrl(rawUrl) {
166
+ try {
167
+ const u = new URL(rawUrl);
168
+ u.hash = '';
169
+ for (const param of TRACKING_PARAMS) {
170
+ u.searchParams.delete(param);
171
+ }
172
+ // Sort remaining params for canonical ordering
173
+ u.searchParams.sort();
174
+ let result = u.toString();
175
+ // Strip trailing slash
176
+ if (result.endsWith('/'))
177
+ result = result.slice(0, -1);
178
+ return result.toLowerCase();
179
+ }
180
+ catch {
181
+ // Not a valid URL — return as-is lowercased
182
+ return rawUrl.toLowerCase().replace(/\/$/, '');
183
+ }
184
+ }