webpeel 0.19.4 → 0.20.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 (544) hide show
  1. package/README.md +2 -2
  2. package/dist/cache.d.ts +0 -1
  3. package/dist/cache.js +0 -1
  4. package/dist/cli/commands/auth.d.ts +5 -0
  5. package/dist/cli/commands/auth.js +476 -0
  6. package/dist/cli/commands/fetch.d.ts +6 -0
  7. package/dist/cli/commands/fetch.js +1015 -0
  8. package/dist/cli/commands/interact.d.ts +5 -0
  9. package/dist/cli/commands/interact.js +839 -0
  10. package/dist/cli/commands/jobs.d.ts +5 -0
  11. package/dist/cli/commands/jobs.js +997 -0
  12. package/dist/cli/commands/screenshot.d.ts +5 -0
  13. package/dist/cli/commands/screenshot.js +273 -0
  14. package/dist/cli/commands/search.d.ts +5 -0
  15. package/dist/cli/commands/search.js +524 -0
  16. package/dist/cli/utils.d.ts +84 -0
  17. package/dist/cli/utils.js +686 -0
  18. package/dist/cli-auth.d.ts +0 -1
  19. package/dist/cli-auth.js +0 -1
  20. package/dist/cli.d.ts +7 -6
  21. package/dist/cli.js +35 -4698
  22. package/dist/core/actions.d.ts +0 -1
  23. package/dist/core/actions.js +0 -1
  24. package/dist/core/agent.d.ts +0 -1
  25. package/dist/core/agent.js +9 -12
  26. package/dist/core/answer.d.ts +0 -1
  27. package/dist/core/answer.js +0 -1
  28. package/dist/core/application-tracker.d.ts +0 -1
  29. package/dist/core/application-tracker.js +0 -1
  30. package/dist/core/apply.d.ts +0 -1
  31. package/dist/core/apply.js +0 -1
  32. package/dist/core/auto-extract.d.ts +0 -1
  33. package/dist/core/auto-extract.js +0 -1
  34. package/dist/core/auto-interact.d.ts +0 -1
  35. package/dist/core/auto-interact.js +0 -1
  36. package/dist/core/bm25-filter.d.ts +0 -1
  37. package/dist/core/bm25-filter.js +0 -1
  38. package/dist/core/branding.d.ts +0 -1
  39. package/dist/core/branding.js +0 -1
  40. package/dist/core/browser-fetch.d.ts +0 -1
  41. package/dist/core/browser-fetch.js +17 -10
  42. package/dist/core/browser-pool.d.ts +0 -1
  43. package/dist/core/browser-pool.js +0 -1
  44. package/dist/core/budget.d.ts +0 -1
  45. package/dist/core/budget.js +0 -1
  46. package/dist/core/cache.d.ts +0 -1
  47. package/dist/core/cache.js +0 -1
  48. package/dist/core/cf-worker-proxy.d.ts +0 -1
  49. package/dist/core/cf-worker-proxy.js +0 -1
  50. package/dist/core/challenge-detection.d.ts +0 -1
  51. package/dist/core/challenge-detection.js +0 -1
  52. package/dist/core/change-tracking.d.ts +0 -1
  53. package/dist/core/change-tracking.js +0 -1
  54. package/dist/core/chunker.d.ts +0 -1
  55. package/dist/core/chunker.js +0 -1
  56. package/dist/core/chunking.d.ts +0 -1
  57. package/dist/core/chunking.js +0 -1
  58. package/dist/core/cloak-fetch.d.ts +0 -1
  59. package/dist/core/cloak-fetch.js +0 -1
  60. package/dist/core/content-pruner.d.ts +0 -1
  61. package/dist/core/content-pruner.js +0 -1
  62. package/dist/core/crawl-checkpoint.d.ts +0 -1
  63. package/dist/core/crawl-checkpoint.js +0 -1
  64. package/dist/core/crawler.d.ts +0 -1
  65. package/dist/core/crawler.js +6 -5
  66. package/dist/core/cycle-fetch.d.ts +0 -1
  67. package/dist/core/cycle-fetch.js +0 -1
  68. package/dist/core/deep-fetch.d.ts +0 -1
  69. package/dist/core/deep-fetch.js +0 -1
  70. package/dist/core/design-analysis.d.ts +0 -1
  71. package/dist/core/design-analysis.js +0 -1
  72. package/dist/core/design-compare.d.ts +0 -1
  73. package/dist/core/design-compare.js +0 -1
  74. package/dist/core/diff.d.ts +0 -1
  75. package/dist/core/diff.js +0 -1
  76. package/dist/core/dns-cache.d.ts +0 -1
  77. package/dist/core/dns-cache.js +0 -1
  78. package/dist/core/documents.d.ts +0 -1
  79. package/dist/core/documents.js +0 -1
  80. package/dist/core/domain-extractors.d.ts +0 -1
  81. package/dist/core/domain-extractors.js +0 -1
  82. package/dist/core/extract-inline.d.ts +0 -1
  83. package/dist/core/extract-inline.js +0 -1
  84. package/dist/core/extract-listings.d.ts +0 -1
  85. package/dist/core/extract-listings.js +0 -1
  86. package/dist/core/extract.d.ts +0 -1
  87. package/dist/core/extract.js +0 -1
  88. package/dist/core/fetcher.d.ts +0 -1
  89. package/dist/core/fetcher.js +0 -1
  90. package/dist/core/google-cache.d.ts +0 -1
  91. package/dist/core/google-cache.js +0 -1
  92. package/dist/core/hotel-search.d.ts +0 -1
  93. package/dist/core/hotel-search.js +0 -1
  94. package/dist/core/http-fetch.d.ts +0 -1
  95. package/dist/core/http-fetch.js +5 -7
  96. package/dist/core/human.d.ts +0 -1
  97. package/dist/core/human.js +0 -1
  98. package/dist/core/jobs.d.ts +0 -1
  99. package/dist/core/jobs.js +0 -1
  100. package/dist/core/json-ld.d.ts +0 -1
  101. package/dist/core/json-ld.js +0 -1
  102. package/dist/core/llm-extract.d.ts +0 -1
  103. package/dist/core/llm-extract.js +0 -1
  104. package/dist/core/logger.d.ts +17 -0
  105. package/dist/core/logger.js +44 -0
  106. package/dist/core/map.d.ts +0 -1
  107. package/dist/core/map.js +0 -1
  108. package/dist/core/markdown.d.ts +0 -1
  109. package/dist/core/markdown.js +0 -1
  110. package/dist/core/metadata.d.ts +0 -1
  111. package/dist/core/metadata.js +0 -1
  112. package/dist/core/paginate.d.ts +0 -1
  113. package/dist/core/paginate.js +0 -1
  114. package/dist/core/pdf.d.ts +0 -1
  115. package/dist/core/pdf.js +0 -1
  116. package/dist/core/peel-tls.d.ts +0 -1
  117. package/dist/core/peel-tls.js +0 -1
  118. package/dist/core/pipeline.d.ts +0 -1
  119. package/dist/core/pipeline.js +22 -25
  120. package/dist/core/profiles.d.ts +0 -1
  121. package/dist/core/profiles.js +0 -1
  122. package/dist/core/quick-answer.d.ts +0 -1
  123. package/dist/core/quick-answer.js +0 -1
  124. package/dist/core/rate-governor.d.ts +0 -1
  125. package/dist/core/rate-governor.js +0 -1
  126. package/dist/core/readability.d.ts +0 -1
  127. package/dist/core/readability.js +0 -1
  128. package/dist/core/research.d.ts +0 -1
  129. package/dist/core/research.js +0 -1
  130. package/dist/core/schema-extraction.d.ts +0 -1
  131. package/dist/core/schema-extraction.js +0 -1
  132. package/dist/core/schema-postprocess.d.ts +0 -1
  133. package/dist/core/schema-postprocess.js +0 -1
  134. package/dist/core/schema-templates.d.ts +0 -1
  135. package/dist/core/schema-templates.js +0 -1
  136. package/dist/core/screenshot.d.ts +0 -1
  137. package/dist/core/screenshot.js +0 -1
  138. package/dist/core/search-fallback.d.ts +0 -1
  139. package/dist/core/search-fallback.js +0 -1
  140. package/dist/core/search-provider.d.ts +0 -1
  141. package/dist/core/search-provider.js +18 -21
  142. package/dist/core/site-search.d.ts +0 -1
  143. package/dist/core/site-search.js +0 -1
  144. package/dist/core/sitemap.d.ts +0 -1
  145. package/dist/core/sitemap.js +0 -1
  146. package/dist/core/stealth-patches.d.ts +0 -1
  147. package/dist/core/stealth-patches.js +0 -1
  148. package/dist/core/stemmer.d.ts +0 -1
  149. package/dist/core/stemmer.js +0 -1
  150. package/dist/core/strategies.d.ts +6 -1
  151. package/dist/core/strategies.js +29 -41
  152. package/dist/core/strategy-hooks.d.ts +0 -1
  153. package/dist/core/strategy-hooks.js +0 -1
  154. package/dist/core/summarize.d.ts +0 -1
  155. package/dist/core/summarize.js +0 -1
  156. package/dist/core/synonyms.d.ts +0 -1
  157. package/dist/core/synonyms.js +0 -1
  158. package/dist/core/table-format.d.ts +0 -1
  159. package/dist/core/table-format.js +0 -1
  160. package/dist/core/timing.d.ts +0 -1
  161. package/dist/core/timing.js +0 -1
  162. package/dist/core/user-agents.d.ts +0 -1
  163. package/dist/core/user-agents.js +0 -1
  164. package/dist/core/watch-manager.d.ts +0 -1
  165. package/dist/core/watch-manager.js +0 -1
  166. package/dist/core/watch.d.ts +0 -1
  167. package/dist/core/watch.js +0 -1
  168. package/dist/core/youtube.d.ts +0 -1
  169. package/dist/core/youtube.js +0 -1
  170. package/dist/index.d.ts +8 -3
  171. package/dist/index.js +27 -3
  172. package/dist/integrations/index.d.ts +0 -1
  173. package/dist/integrations/index.js +0 -1
  174. package/dist/integrations/langchain.d.ts +0 -1
  175. package/dist/integrations/langchain.js +0 -1
  176. package/dist/integrations/llamaindex.d.ts +0 -1
  177. package/dist/integrations/llamaindex.js +0 -1
  178. package/dist/mcp/handlers/act.d.ts +5 -0
  179. package/dist/mcp/handlers/act.js +34 -0
  180. package/dist/mcp/handlers/definitions.d.ts +6 -0
  181. package/dist/mcp/handlers/definitions.js +266 -0
  182. package/dist/mcp/handlers/extract.d.ts +6 -0
  183. package/dist/mcp/handlers/extract.js +102 -0
  184. package/dist/mcp/handlers/fetch.d.ts +6 -0
  185. package/dist/mcp/handlers/fetch.js +98 -0
  186. package/dist/mcp/handlers/find.d.ts +5 -0
  187. package/dist/mcp/handlers/find.js +137 -0
  188. package/dist/mcp/handlers/index.d.ts +13 -0
  189. package/dist/mcp/handlers/index.js +61 -0
  190. package/dist/mcp/handlers/legacy.d.ts +25 -0
  191. package/dist/mcp/handlers/legacy.js +450 -0
  192. package/dist/mcp/handlers/meta.d.ts +6 -0
  193. package/dist/mcp/handlers/meta.js +31 -0
  194. package/dist/mcp/handlers/monitor.d.ts +5 -0
  195. package/dist/mcp/handlers/monitor.js +41 -0
  196. package/dist/mcp/handlers/read.d.ts +6 -0
  197. package/dist/mcp/handlers/read.js +63 -0
  198. package/dist/mcp/handlers/see.d.ts +5 -0
  199. package/dist/mcp/handlers/see.js +75 -0
  200. package/dist/mcp/handlers/types.d.ts +29 -0
  201. package/dist/mcp/handlers/types.js +28 -0
  202. package/dist/mcp/server.d.ts +3 -4
  203. package/dist/mcp/server.js +35 -1101
  204. package/dist/mcp/smart-router.d.ts +0 -1
  205. package/dist/mcp/smart-router.js +3 -1
  206. package/dist/types.d.ts +6 -1
  207. package/dist/types.js +0 -1
  208. package/package.json +3 -13
  209. package/dist/cache.d.ts.map +0 -1
  210. package/dist/cache.js.map +0 -1
  211. package/dist/cli-auth.d.ts.map +0 -1
  212. package/dist/cli-auth.js.map +0 -1
  213. package/dist/cli.bundle.cjs +0 -159248
  214. package/dist/cli.d.ts.map +0 -1
  215. package/dist/cli.js.map +0 -1
  216. package/dist/core/actions.d.ts.map +0 -1
  217. package/dist/core/actions.js.map +0 -1
  218. package/dist/core/agent.d.ts.map +0 -1
  219. package/dist/core/agent.js.map +0 -1
  220. package/dist/core/answer.d.ts.map +0 -1
  221. package/dist/core/answer.js.map +0 -1
  222. package/dist/core/application-tracker.d.ts.map +0 -1
  223. package/dist/core/application-tracker.js.map +0 -1
  224. package/dist/core/apply.d.ts.map +0 -1
  225. package/dist/core/apply.js.map +0 -1
  226. package/dist/core/auto-extract.d.ts.map +0 -1
  227. package/dist/core/auto-extract.js.map +0 -1
  228. package/dist/core/auto-interact.d.ts.map +0 -1
  229. package/dist/core/auto-interact.js.map +0 -1
  230. package/dist/core/bm25-filter.d.ts.map +0 -1
  231. package/dist/core/bm25-filter.js.map +0 -1
  232. package/dist/core/branding.d.ts.map +0 -1
  233. package/dist/core/branding.js.map +0 -1
  234. package/dist/core/browser-fetch.d.ts.map +0 -1
  235. package/dist/core/browser-fetch.js.map +0 -1
  236. package/dist/core/browser-pool.d.ts.map +0 -1
  237. package/dist/core/browser-pool.js.map +0 -1
  238. package/dist/core/budget.d.ts.map +0 -1
  239. package/dist/core/budget.js.map +0 -1
  240. package/dist/core/cache.d.ts.map +0 -1
  241. package/dist/core/cache.js.map +0 -1
  242. package/dist/core/cf-worker-proxy.d.ts.map +0 -1
  243. package/dist/core/cf-worker-proxy.js.map +0 -1
  244. package/dist/core/challenge-detection.d.ts.map +0 -1
  245. package/dist/core/challenge-detection.js.map +0 -1
  246. package/dist/core/change-tracking.d.ts.map +0 -1
  247. package/dist/core/change-tracking.js.map +0 -1
  248. package/dist/core/chunker.d.ts.map +0 -1
  249. package/dist/core/chunker.js.map +0 -1
  250. package/dist/core/chunking.d.ts.map +0 -1
  251. package/dist/core/chunking.js.map +0 -1
  252. package/dist/core/cloak-fetch.d.ts.map +0 -1
  253. package/dist/core/cloak-fetch.js.map +0 -1
  254. package/dist/core/content-pruner.d.ts.map +0 -1
  255. package/dist/core/content-pruner.js.map +0 -1
  256. package/dist/core/crawl-checkpoint.d.ts.map +0 -1
  257. package/dist/core/crawl-checkpoint.js.map +0 -1
  258. package/dist/core/crawler.d.ts.map +0 -1
  259. package/dist/core/crawler.js.map +0 -1
  260. package/dist/core/cycle-fetch.d.ts.map +0 -1
  261. package/dist/core/cycle-fetch.js.map +0 -1
  262. package/dist/core/deep-fetch.d.ts.map +0 -1
  263. package/dist/core/deep-fetch.js.map +0 -1
  264. package/dist/core/design-analysis.d.ts.map +0 -1
  265. package/dist/core/design-analysis.js.map +0 -1
  266. package/dist/core/design-compare.d.ts.map +0 -1
  267. package/dist/core/design-compare.js.map +0 -1
  268. package/dist/core/diff.d.ts.map +0 -1
  269. package/dist/core/diff.js.map +0 -1
  270. package/dist/core/dns-cache.d.ts.map +0 -1
  271. package/dist/core/dns-cache.js.map +0 -1
  272. package/dist/core/documents.d.ts.map +0 -1
  273. package/dist/core/documents.js.map +0 -1
  274. package/dist/core/domain-extractors.d.ts.map +0 -1
  275. package/dist/core/domain-extractors.js.map +0 -1
  276. package/dist/core/extract-inline.d.ts.map +0 -1
  277. package/dist/core/extract-inline.js.map +0 -1
  278. package/dist/core/extract-listings.d.ts.map +0 -1
  279. package/dist/core/extract-listings.js.map +0 -1
  280. package/dist/core/extract.d.ts.map +0 -1
  281. package/dist/core/extract.js.map +0 -1
  282. package/dist/core/fetcher.d.ts.map +0 -1
  283. package/dist/core/fetcher.js.map +0 -1
  284. package/dist/core/google-cache.d.ts.map +0 -1
  285. package/dist/core/google-cache.js.map +0 -1
  286. package/dist/core/hotel-search.d.ts.map +0 -1
  287. package/dist/core/hotel-search.js.map +0 -1
  288. package/dist/core/http-fetch.d.ts.map +0 -1
  289. package/dist/core/http-fetch.js.map +0 -1
  290. package/dist/core/human.d.ts.map +0 -1
  291. package/dist/core/human.js.map +0 -1
  292. package/dist/core/jobs.d.ts.map +0 -1
  293. package/dist/core/jobs.js.map +0 -1
  294. package/dist/core/json-ld.d.ts.map +0 -1
  295. package/dist/core/json-ld.js.map +0 -1
  296. package/dist/core/llm-extract.d.ts.map +0 -1
  297. package/dist/core/llm-extract.js.map +0 -1
  298. package/dist/core/map.d.ts.map +0 -1
  299. package/dist/core/map.js.map +0 -1
  300. package/dist/core/markdown.d.ts.map +0 -1
  301. package/dist/core/markdown.js.map +0 -1
  302. package/dist/core/metadata.d.ts.map +0 -1
  303. package/dist/core/metadata.js.map +0 -1
  304. package/dist/core/paginate.d.ts.map +0 -1
  305. package/dist/core/paginate.js.map +0 -1
  306. package/dist/core/pdf.d.ts.map +0 -1
  307. package/dist/core/pdf.js.map +0 -1
  308. package/dist/core/peel-tls.d.ts.map +0 -1
  309. package/dist/core/peel-tls.js.map +0 -1
  310. package/dist/core/pipeline.d.ts.map +0 -1
  311. package/dist/core/pipeline.js.map +0 -1
  312. package/dist/core/profiles.d.ts.map +0 -1
  313. package/dist/core/profiles.js.map +0 -1
  314. package/dist/core/quick-answer.d.ts.map +0 -1
  315. package/dist/core/quick-answer.js.map +0 -1
  316. package/dist/core/rate-governor.d.ts.map +0 -1
  317. package/dist/core/rate-governor.js.map +0 -1
  318. package/dist/core/readability.d.ts.map +0 -1
  319. package/dist/core/readability.js.map +0 -1
  320. package/dist/core/research.d.ts.map +0 -1
  321. package/dist/core/research.js.map +0 -1
  322. package/dist/core/schema-extraction.d.ts.map +0 -1
  323. package/dist/core/schema-extraction.js.map +0 -1
  324. package/dist/core/schema-postprocess.d.ts.map +0 -1
  325. package/dist/core/schema-postprocess.js.map +0 -1
  326. package/dist/core/schema-templates.d.ts.map +0 -1
  327. package/dist/core/schema-templates.js.map +0 -1
  328. package/dist/core/screenshot.d.ts.map +0 -1
  329. package/dist/core/screenshot.js.map +0 -1
  330. package/dist/core/search-fallback.d.ts.map +0 -1
  331. package/dist/core/search-fallback.js.map +0 -1
  332. package/dist/core/search-provider.d.ts.map +0 -1
  333. package/dist/core/search-provider.js.map +0 -1
  334. package/dist/core/site-search.d.ts.map +0 -1
  335. package/dist/core/site-search.js.map +0 -1
  336. package/dist/core/sitemap.d.ts.map +0 -1
  337. package/dist/core/sitemap.js.map +0 -1
  338. package/dist/core/stealth-patches.d.ts.map +0 -1
  339. package/dist/core/stealth-patches.js.map +0 -1
  340. package/dist/core/stemmer.d.ts.map +0 -1
  341. package/dist/core/stemmer.js.map +0 -1
  342. package/dist/core/strategies.d.ts.map +0 -1
  343. package/dist/core/strategies.js.map +0 -1
  344. package/dist/core/strategy-hooks.d.ts.map +0 -1
  345. package/dist/core/strategy-hooks.js.map +0 -1
  346. package/dist/core/summarize.d.ts.map +0 -1
  347. package/dist/core/summarize.js.map +0 -1
  348. package/dist/core/synonyms.d.ts.map +0 -1
  349. package/dist/core/synonyms.js.map +0 -1
  350. package/dist/core/table-format.d.ts.map +0 -1
  351. package/dist/core/table-format.js.map +0 -1
  352. package/dist/core/timing.d.ts.map +0 -1
  353. package/dist/core/timing.js.map +0 -1
  354. package/dist/core/user-agents.d.ts.map +0 -1
  355. package/dist/core/user-agents.js.map +0 -1
  356. package/dist/core/watch-manager.d.ts.map +0 -1
  357. package/dist/core/watch-manager.js.map +0 -1
  358. package/dist/core/watch.d.ts.map +0 -1
  359. package/dist/core/watch.js.map +0 -1
  360. package/dist/core/youtube.d.ts.map +0 -1
  361. package/dist/core/youtube.js.map +0 -1
  362. package/dist/index.d.ts.map +0 -1
  363. package/dist/index.js.map +0 -1
  364. package/dist/integrations/index.d.ts.map +0 -1
  365. package/dist/integrations/index.js.map +0 -1
  366. package/dist/integrations/langchain.d.ts.map +0 -1
  367. package/dist/integrations/langchain.js.map +0 -1
  368. package/dist/integrations/llamaindex.d.ts.map +0 -1
  369. package/dist/integrations/llamaindex.js.map +0 -1
  370. package/dist/mcp/server.d.ts.map +0 -1
  371. package/dist/mcp/server.js.map +0 -1
  372. package/dist/mcp/smart-router.d.ts.map +0 -1
  373. package/dist/mcp/smart-router.js.map +0 -1
  374. package/dist/server/app.d.ts +0 -15
  375. package/dist/server/app.d.ts.map +0 -1
  376. package/dist/server/app.js +0 -350
  377. package/dist/server/app.js.map +0 -1
  378. package/dist/server/auth-store.d.ts +0 -28
  379. package/dist/server/auth-store.d.ts.map +0 -1
  380. package/dist/server/auth-store.js +0 -89
  381. package/dist/server/auth-store.js.map +0 -1
  382. package/dist/server/email-service.d.ts +0 -22
  383. package/dist/server/email-service.d.ts.map +0 -1
  384. package/dist/server/email-service.js +0 -80
  385. package/dist/server/email-service.js.map +0 -1
  386. package/dist/server/job-queue.d.ts +0 -93
  387. package/dist/server/job-queue.d.ts.map +0 -1
  388. package/dist/server/job-queue.js +0 -146
  389. package/dist/server/job-queue.js.map +0 -1
  390. package/dist/server/logger.d.ts +0 -11
  391. package/dist/server/logger.d.ts.map +0 -1
  392. package/dist/server/logger.js +0 -38
  393. package/dist/server/logger.js.map +0 -1
  394. package/dist/server/middleware/auth.d.ts +0 -29
  395. package/dist/server/middleware/auth.d.ts.map +0 -1
  396. package/dist/server/middleware/auth.js +0 -222
  397. package/dist/server/middleware/auth.js.map +0 -1
  398. package/dist/server/middleware/rate-limit.d.ts +0 -25
  399. package/dist/server/middleware/rate-limit.d.ts.map +0 -1
  400. package/dist/server/middleware/rate-limit.js +0 -168
  401. package/dist/server/middleware/rate-limit.js.map +0 -1
  402. package/dist/server/middleware/url-validator.d.ts +0 -16
  403. package/dist/server/middleware/url-validator.d.ts.map +0 -1
  404. package/dist/server/middleware/url-validator.js +0 -187
  405. package/dist/server/middleware/url-validator.js.map +0 -1
  406. package/dist/server/openapi.yaml +0 -4944
  407. package/dist/server/pg-auth-store.d.ts +0 -133
  408. package/dist/server/pg-auth-store.d.ts.map +0 -1
  409. package/dist/server/pg-auth-store.js +0 -473
  410. package/dist/server/pg-auth-store.js.map +0 -1
  411. package/dist/server/pg-job-queue.d.ts +0 -60
  412. package/dist/server/pg-job-queue.d.ts.map +0 -1
  413. package/dist/server/pg-job-queue.js +0 -365
  414. package/dist/server/pg-job-queue.js.map +0 -1
  415. package/dist/server/premium/domain-intel.d.ts +0 -17
  416. package/dist/server/premium/domain-intel.d.ts.map +0 -1
  417. package/dist/server/premium/domain-intel.js +0 -134
  418. package/dist/server/premium/domain-intel.js.map +0 -1
  419. package/dist/server/premium/index.d.ts +0 -18
  420. package/dist/server/premium/index.d.ts.map +0 -1
  421. package/dist/server/premium/index.js +0 -36
  422. package/dist/server/premium/index.js.map +0 -1
  423. package/dist/server/premium/swr-cache.d.ts +0 -15
  424. package/dist/server/premium/swr-cache.d.ts.map +0 -1
  425. package/dist/server/premium/swr-cache.js +0 -35
  426. package/dist/server/premium/swr-cache.js.map +0 -1
  427. package/dist/server/routes/activity.d.ts +0 -7
  428. package/dist/server/routes/activity.d.ts.map +0 -1
  429. package/dist/server/routes/activity.js +0 -68
  430. package/dist/server/routes/activity.js.map +0 -1
  431. package/dist/server/routes/agent.d.ts +0 -16
  432. package/dist/server/routes/agent.d.ts.map +0 -1
  433. package/dist/server/routes/agent.js +0 -247
  434. package/dist/server/routes/agent.js.map +0 -1
  435. package/dist/server/routes/answer.d.ts +0 -6
  436. package/dist/server/routes/answer.d.ts.map +0 -1
  437. package/dist/server/routes/answer.js +0 -133
  438. package/dist/server/routes/answer.js.map +0 -1
  439. package/dist/server/routes/ask.d.ts +0 -23
  440. package/dist/server/routes/ask.d.ts.map +0 -1
  441. package/dist/server/routes/ask.js +0 -119
  442. package/dist/server/routes/ask.js.map +0 -1
  443. package/dist/server/routes/batch.d.ts +0 -7
  444. package/dist/server/routes/batch.d.ts.map +0 -1
  445. package/dist/server/routes/batch.js +0 -412
  446. package/dist/server/routes/batch.js.map +0 -1
  447. package/dist/server/routes/cli-usage.d.ts +0 -7
  448. package/dist/server/routes/cli-usage.d.ts.map +0 -1
  449. package/dist/server/routes/cli-usage.js +0 -121
  450. package/dist/server/routes/cli-usage.js.map +0 -1
  451. package/dist/server/routes/compat.d.ts +0 -24
  452. package/dist/server/routes/compat.d.ts.map +0 -1
  453. package/dist/server/routes/compat.js +0 -653
  454. package/dist/server/routes/compat.js.map +0 -1
  455. package/dist/server/routes/deep-fetch.d.ts +0 -9
  456. package/dist/server/routes/deep-fetch.d.ts.map +0 -1
  457. package/dist/server/routes/deep-fetch.js +0 -50
  458. package/dist/server/routes/deep-fetch.js.map +0 -1
  459. package/dist/server/routes/demo.d.ts +0 -25
  460. package/dist/server/routes/demo.d.ts.map +0 -1
  461. package/dist/server/routes/demo.js +0 -434
  462. package/dist/server/routes/demo.js.map +0 -1
  463. package/dist/server/routes/extract.d.ts +0 -9
  464. package/dist/server/routes/extract.d.ts.map +0 -1
  465. package/dist/server/routes/extract.js +0 -150
  466. package/dist/server/routes/extract.js.map +0 -1
  467. package/dist/server/routes/fetch.d.ts +0 -8
  468. package/dist/server/routes/fetch.d.ts.map +0 -1
  469. package/dist/server/routes/fetch.js +0 -988
  470. package/dist/server/routes/fetch.js.map +0 -1
  471. package/dist/server/routes/health.d.ts +0 -8
  472. package/dist/server/routes/health.d.ts.map +0 -1
  473. package/dist/server/routes/health.js +0 -20
  474. package/dist/server/routes/health.js.map +0 -1
  475. package/dist/server/routes/jobs.d.ts +0 -8
  476. package/dist/server/routes/jobs.d.ts.map +0 -1
  477. package/dist/server/routes/jobs.js +0 -487
  478. package/dist/server/routes/jobs.js.map +0 -1
  479. package/dist/server/routes/mcp.d.ts +0 -18
  480. package/dist/server/routes/mcp.d.ts.map +0 -1
  481. package/dist/server/routes/mcp.js +0 -1260
  482. package/dist/server/routes/mcp.js.map +0 -1
  483. package/dist/server/routes/oauth.d.ts +0 -10
  484. package/dist/server/routes/oauth.d.ts.map +0 -1
  485. package/dist/server/routes/oauth.js +0 -334
  486. package/dist/server/routes/oauth.js.map +0 -1
  487. package/dist/server/routes/quick-answer.d.ts +0 -9
  488. package/dist/server/routes/quick-answer.d.ts.map +0 -1
  489. package/dist/server/routes/quick-answer.js +0 -93
  490. package/dist/server/routes/quick-answer.js.map +0 -1
  491. package/dist/server/routes/screenshot.d.ts +0 -23
  492. package/dist/server/routes/screenshot.d.ts.map +0 -1
  493. package/dist/server/routes/screenshot.js +0 -819
  494. package/dist/server/routes/screenshot.js.map +0 -1
  495. package/dist/server/routes/search.d.ts +0 -7
  496. package/dist/server/routes/search.d.ts.map +0 -1
  497. package/dist/server/routes/search.js +0 -312
  498. package/dist/server/routes/search.js.map +0 -1
  499. package/dist/server/routes/session.d.ts +0 -16
  500. package/dist/server/routes/session.d.ts.map +0 -1
  501. package/dist/server/routes/session.js +0 -278
  502. package/dist/server/routes/session.js.map +0 -1
  503. package/dist/server/routes/stats.d.ts +0 -7
  504. package/dist/server/routes/stats.d.ts.map +0 -1
  505. package/dist/server/routes/stats.js +0 -65
  506. package/dist/server/routes/stats.js.map +0 -1
  507. package/dist/server/routes/stripe.d.ts +0 -16
  508. package/dist/server/routes/stripe.d.ts.map +0 -1
  509. package/dist/server/routes/stripe.js +0 -283
  510. package/dist/server/routes/stripe.js.map +0 -1
  511. package/dist/server/routes/users.d.ts +0 -9
  512. package/dist/server/routes/users.d.ts.map +0 -1
  513. package/dist/server/routes/users.js +0 -1211
  514. package/dist/server/routes/users.js.map +0 -1
  515. package/dist/server/routes/watch.d.ts +0 -16
  516. package/dist/server/routes/watch.d.ts.map +0 -1
  517. package/dist/server/routes/watch.js +0 -257
  518. package/dist/server/routes/watch.js.map +0 -1
  519. package/dist/server/routes/webhooks.d.ts +0 -16
  520. package/dist/server/routes/webhooks.d.ts.map +0 -1
  521. package/dist/server/routes/webhooks.js +0 -74
  522. package/dist/server/routes/webhooks.js.map +0 -1
  523. package/dist/server/routes/youtube.d.ts +0 -7
  524. package/dist/server/routes/youtube.d.ts.map +0 -1
  525. package/dist/server/routes/youtube.js +0 -93
  526. package/dist/server/routes/youtube.js.map +0 -1
  527. package/dist/server/sentry.d.ts +0 -14
  528. package/dist/server/sentry.d.ts.map +0 -1
  529. package/dist/server/sentry.js +0 -39
  530. package/dist/server/sentry.js.map +0 -1
  531. package/dist/server/types.d.ts +0 -16
  532. package/dist/server/types.d.ts.map +0 -1
  533. package/dist/server/types.js +0 -8
  534. package/dist/server/types.js.map +0 -1
  535. package/dist/server/utils/response.d.ts +0 -45
  536. package/dist/server/utils/response.d.ts.map +0 -1
  537. package/dist/server/utils/response.js +0 -70
  538. package/dist/server/utils/response.js.map +0 -1
  539. package/dist/server/utils/sse.d.ts +0 -23
  540. package/dist/server/utils/sse.d.ts.map +0 -1
  541. package/dist/server/utils/sse.js +0 -39
  542. package/dist/server/utils/sse.js.map +0 -1
  543. package/dist/types.d.ts.map +0 -1
  544. package/dist/types.js.map +0 -1
@@ -1,988 +0,0 @@
1
- /**
2
- * Fetch endpoint with caching
3
- */
4
- import { Router } from 'express';
5
- import '../types.js'; // Augments Express.Request with requestId
6
- import { peel } from '../../index.js';
7
- import { normalizeActions } from '../../core/actions.js';
8
- import { extractInlineJson } from '../../core/extract-inline.js';
9
- import { LRUCache } from 'lru-cache';
10
- import { validateUrlForSSRF, SSRFError } from '../middleware/url-validator.js';
11
- import { wantsEnvelope, successResponse } from '../utils/response.js';
12
- import { getSchemaTemplate } from '../../core/schema-templates.js';
13
- import { quickAnswer } from '../../core/quick-answer.js';
14
- import { sendUsageAlertEmail } from '../email-service.js';
15
- // ── Helper: extractive summarizer (TF-IDF-like sentence scoring) ─────────────
16
- function extractSummary(content, maxWords = 150) {
17
- if (!content)
18
- return '';
19
- const sentences = content
20
- .split(/(?<=[.!?])\s+/)
21
- .map(s => s.trim())
22
- .filter(s => s.length > 40 && s.length < 600);
23
- if (sentences.length === 0) {
24
- const words = content.split(/\s+/);
25
- return words.slice(0, maxWords).join(' ') + (words.length > maxWords ? '\u2026' : '');
26
- }
27
- if (sentences.length <= 3)
28
- return sentences.join(' ');
29
- const allWords = content.toLowerCase().split(/\W+/).filter(w => w.length > 3);
30
- const wordFreq = {};
31
- for (const w of allWords)
32
- wordFreq[w] = (wordFreq[w] || 0) + 1;
33
- const maxFreq = Math.max(1, ...Object.values(wordFreq));
34
- const scored = sentences.map((sentence, idx) => {
35
- const words = sentence.toLowerCase().split(/\W+/).filter(w => w.length > 3);
36
- const score = words.reduce((sum, w) => sum + (wordFreq[w] || 0) / maxFreq, 0) / Math.max(1, words.length);
37
- const posBonus = idx === 0 ? 0.3 : idx === sentences.length - 1 ? 0.1 : 0;
38
- return { sentence, score: score + posBonus, idx };
39
- });
40
- scored.sort((a, b) => b.score - a.score);
41
- const selected = [];
42
- let wc = 0;
43
- for (const item of scored) {
44
- const itemWc = item.sentence.split(/\s+/).length;
45
- if (wc + itemWc > maxWords * 1.3)
46
- break;
47
- selected.push(item);
48
- wc += itemWc;
49
- if (selected.length >= 5)
50
- break;
51
- }
52
- selected.sort((a, b) => a.idx - b.idx);
53
- return selected.map(s => s.sentence).join(' ');
54
- }
55
- // ── Helper: check usage and determine if alert email should be sent ───────────
56
- async function checkAndTriggerAlert(pgStore, userId) {
57
- const getCurrentWeek = () => {
58
- const now = new Date();
59
- const year = now.getUTCFullYear();
60
- const jan4 = new Date(Date.UTC(year, 0, 4));
61
- const weekNum = Math.ceil(((now.getTime() - jan4.getTime()) / 86400000 + jan4.getUTCDay() + 1) / 7);
62
- return `${year}-W${String(weekNum).padStart(2, '0')}`;
63
- };
64
- const currentWeek = getCurrentWeek();
65
- const result = await pgStore.pool.query(`SELECT u.email, u.name, u.tier, u.alert_threshold, u.alert_email, u.alert_sent_at,
66
- u.weekly_limit,
67
- COALESCE(SUM(wu.total_count), 0) AS total_used,
68
- u.weekly_limit + COALESCE(MAX(wu.rollover_credits), 0) AS total_available
69
- FROM users u
70
- LEFT JOIN api_keys ak ON ak.user_id = u.id
71
- LEFT JOIN weekly_usage wu ON wu.api_key_id = ak.id AND wu.week = $2
72
- WHERE u.id = $1
73
- GROUP BY u.id, u.email, u.name, u.tier, u.alert_threshold, u.alert_email, u.alert_sent_at, u.weekly_limit`, [userId, currentWeek]);
74
- const row = result.rows[0];
75
- if (!row || !row.alert_threshold)
76
- return { shouldSendAlert: false };
77
- const used = parseInt(row.total_used, 10) || 0;
78
- const total = parseInt(row.total_available, 10) || row.weekly_limit || 999;
79
- const usagePercent = total > 0 ? Math.round((used / total) * 100) : 0;
80
- // Only alert if: crosses threshold AND haven't sent alert this week
81
- const lastAlert = row.alert_sent_at ? new Date(row.alert_sent_at) : null;
82
- const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
83
- const alreadySentThisWeek = lastAlert !== null && lastAlert > oneWeekAgo;
84
- return {
85
- shouldSendAlert: usagePercent >= row.alert_threshold && !alreadySentThisWeek,
86
- usagePercent,
87
- used,
88
- total,
89
- userEmail: row.email,
90
- userName: row.name || undefined,
91
- userTier: row.tier,
92
- alertEmail: row.alert_email || undefined,
93
- };
94
- }
95
- const VALID_LLM_PROVIDERS = ['openai', 'anthropic', 'google'];
96
- export function createFetchRouter(authStore) {
97
- const router = Router();
98
- // LRU cache: 5 minute TTL, max 500 entries, 100MB total size
99
- const cache = new LRUCache({
100
- max: 500,
101
- ttl: 5 * 60 * 1000, // 5 minutes default
102
- maxSize: 100 * 1024 * 1024, // 100MB
103
- sizeCalculation: (entry) => {
104
- return JSON.stringify(entry).length;
105
- },
106
- });
107
- router.get('/v1/fetch', async (req, res) => {
108
- try {
109
- // Require authentication — API key or JWT session
110
- const userId = req.auth?.keyInfo?.accountId || req.user?.userId;
111
- if (!userId) {
112
- res.status(401).json({
113
- error: 'unauthorized',
114
- message: 'API key required. Get one free at https://app.webpeel.dev/keys',
115
- });
116
- return;
117
- }
118
- const { url, render, wait, format, includeTags, excludeTags, images, location, languages, onlyMainContent, actions, maxAge, storeInCache, stream, noCache, cacheTtl, budget, question, summary, readable, stealth, screenshot, maxTokens, selector, exclude, fullPage, raw, lite, timeout, schema, detail, } = req.query;
119
- const detailMode = detail || 'standard';
120
- // Validate URL parameter
121
- if (!url || typeof url !== 'string') {
122
- res.status(400).json({
123
- success: false,
124
- error: {
125
- type: 'invalid_request',
126
- message: 'Missing or invalid "url" parameter.',
127
- hint: 'Pass a URL as a query parameter: GET /v1/fetch?url=https://example.com',
128
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
129
- },
130
- requestId: req.requestId,
131
- });
132
- return;
133
- }
134
- // SECURITY: Validate URL format and length
135
- if (url.length > 2048) {
136
- res.status(400).json({
137
- success: false,
138
- error: {
139
- type: 'invalid_url',
140
- message: 'URL too long (max 2048 characters)',
141
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
142
- },
143
- requestId: req.requestId,
144
- });
145
- return;
146
- }
147
- try {
148
- const parsed = new URL(url);
149
- // Normalize URL for consistent caching
150
- const normalizedUrl = parsed.href;
151
- // Use normalized URL for cache key
152
- if (normalizedUrl !== url) {
153
- // URL was normalized, update for caching
154
- }
155
- }
156
- catch {
157
- res.status(400).json({
158
- success: false,
159
- error: {
160
- type: 'invalid_url',
161
- message: 'Invalid URL format',
162
- hint: 'Ensure the URL includes a scheme (https://) and a valid hostname',
163
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
164
- },
165
- requestId: req.requestId,
166
- });
167
- return;
168
- }
169
- // SECURITY: Validate URL to prevent SSRF attacks
170
- try {
171
- validateUrlForSSRF(url);
172
- }
173
- catch (error) {
174
- if (error instanceof SSRFError) {
175
- res.status(400).json({
176
- success: false,
177
- error: {
178
- type: 'forbidden_url',
179
- message: 'Cannot fetch localhost, private networks, or non-HTTP URLs',
180
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
181
- },
182
- requestId: req.requestId,
183
- });
184
- return;
185
- }
186
- throw error;
187
- }
188
- // Parse actions query param (JSON-encoded array)
189
- let parsedActions;
190
- if (actions && typeof actions === 'string') {
191
- try {
192
- const raw = JSON.parse(actions);
193
- parsedActions = normalizeActions(raw);
194
- }
195
- catch (e) {
196
- res.status(400).json({
197
- success: false,
198
- error: {
199
- type: 'invalid_request',
200
- message: 'Invalid "actions" parameter: must be a valid JSON array',
201
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
202
- },
203
- requestId: req.requestId,
204
- });
205
- return;
206
- }
207
- }
208
- // Build cache key (include new parameters)
209
- const actionsKey = parsedActions ? JSON.stringify(parsedActions) : '';
210
- const cacheKey = `fetch:${url}:${render}:${wait}:${format}:${includeTags}:${excludeTags}:${images}:${location}:${languages}:${onlyMainContent}:${stream}:${actionsKey}:${budget}:${question}:${summary}:${readable}:${stealth}:${screenshot}:${maxTokens}:${selector}:${exclude}:${fullPage}:${raw}`;
211
- // Cache bypass: ?noCache=true or Cache-Control: no-cache header
212
- const bypassCache = noCache === 'true' || req.headers['cache-control'] === 'no-cache';
213
- // Per-request TTL (cacheTtl in seconds, default 300s = 5 min)
214
- const cacheTtlMs = cacheTtl !== undefined
215
- ? parseInt(cacheTtl, 10) * 1000
216
- : 5 * 60 * 1000;
217
- // Check cache (with maxAge support)
218
- const maxAgeMs = maxAge !== undefined ? parseInt(maxAge, 10) : 172800000; // Default 2 days
219
- if (!bypassCache) {
220
- const cached = cache.get(cacheKey);
221
- if (cached && maxAgeMs > 0) {
222
- const cacheAge = Date.now() - cached.timestamp;
223
- if (cacheAge < maxAgeMs && cacheAge < cacheTtlMs) {
224
- res.setHeader('X-Cache', 'HIT');
225
- res.setHeader('X-Cache-Age', Math.floor(cacheAge / 1000).toString());
226
- if (wantsEnvelope(req)) {
227
- successResponse(res, cached.result, {
228
- requestId: req.requestId,
229
- cached: true,
230
- });
231
- }
232
- else {
233
- res.json(cached.result);
234
- }
235
- return;
236
- }
237
- }
238
- }
239
- // Parse options
240
- const isSoftLimited = req.auth?.softLimited === true;
241
- const hasExtraUsage = req.auth?.extraUsageAvailable === true;
242
- // Parse tag arrays from comma-separated strings
243
- const includeTagsArray = includeTags
244
- ? includeTags.split(',').map(t => t.trim()).filter(Boolean)
245
- : undefined;
246
- const excludeTagsArray = excludeTags
247
- ? excludeTags.split(',').map(t => t.trim()).filter(Boolean)
248
- : undefined;
249
- const languagesArray = languages
250
- ? languages.split(',').map(l => l.trim()).filter(Boolean)
251
- : undefined;
252
- // onlyMainContent is a shortcut for common include tags
253
- const finalIncludeTags = onlyMainContent === 'true'
254
- ? ['main', 'article', '.content', '#content']
255
- : includeTagsArray;
256
- // When actions are present, force browser mode (skip HTTP fast path)
257
- const hasActions = parsedActions && parsedActions.length > 0;
258
- const shouldRender = hasActions || render === 'true';
259
- const options = {
260
- // SOFT LIMIT: When over quota AND no extra usage, force HTTP-only
261
- // If extra usage is available, allow full functionality
262
- // Exception: actions always require render
263
- render: (isSoftLimited && !hasExtraUsage && !hasActions) ? false : shouldRender,
264
- wait: (isSoftLimited && !hasExtraUsage) ? 0 : (wait ? parseInt(wait, 10) : undefined),
265
- format: format || 'markdown',
266
- stream: stream === 'true',
267
- includeTags: finalIncludeTags,
268
- excludeTags: excludeTagsArray,
269
- images: images === 'true',
270
- actions: parsedActions,
271
- location: location || languagesArray ? {
272
- country: location,
273
- languages: languagesArray,
274
- } : undefined,
275
- budget: budget ? parseInt(budget, 10) : undefined,
276
- question: question,
277
- readable: readable === 'true',
278
- stealth: (isSoftLimited && !hasExtraUsage) ? false : stealth === 'true',
279
- screenshot: (isSoftLimited && !hasExtraUsage) ? false : screenshot === 'true',
280
- maxTokens: maxTokens ? parseInt(maxTokens, 10) : undefined,
281
- selector: selector,
282
- exclude: exclude ? exclude.split(',').map(s => s.trim()).filter(Boolean) : undefined,
283
- fullPage: fullPage === 'true',
284
- raw: raw === 'true',
285
- lite: lite === 'true',
286
- timeout: timeout ? parseInt(timeout, 10) : undefined,
287
- };
288
- // Auto-budget: default to 4000 tokens for API requests when no budget specified
289
- // Opt-out: budget=0 explicitly disables. Lite mode disables auto-budget.
290
- if (options.budget === undefined && !options.lite) {
291
- options.budget = 4000;
292
- res.setHeader('X-Auto-Budget', '4000');
293
- }
294
- // Inform the user if their request was degraded
295
- if (isSoftLimited && !hasExtraUsage && render === 'true' && !hasActions) {
296
- res.setHeader('X-Degraded', 'render=true downgraded to HTTP-only (quota exceeded)');
297
- }
298
- if (isSoftLimited && !hasExtraUsage && stealth === 'true') {
299
- res.setHeader('X-Degraded', 'stealth=true downgraded (quota exceeded)');
300
- }
301
- if (isSoftLimited && !hasExtraUsage && screenshot === 'true') {
302
- res.setHeader('X-Degraded', 'screenshot=true downgraded (quota exceeded)');
303
- }
304
- // Validate wait parameter
305
- if (options.wait !== undefined && (isNaN(options.wait) || options.wait < 0 || options.wait > 60000)) {
306
- res.status(400).json({
307
- success: false,
308
- error: {
309
- type: 'invalid_request',
310
- message: 'Invalid "wait" parameter: must be between 0 and 60000ms',
311
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
312
- },
313
- requestId: req.requestId,
314
- });
315
- return;
316
- }
317
- // Validate format parameter
318
- if (!['markdown', 'text', 'html', 'clean'].includes(options.format || '')) {
319
- res.status(400).json({
320
- success: false,
321
- error: {
322
- type: 'invalid_request',
323
- message: 'Invalid "format" parameter: must be "markdown", "text", "html", or "clean"',
324
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
325
- },
326
- requestId: req.requestId,
327
- });
328
- return;
329
- }
330
- const shouldStream = options.stream === true;
331
- if (shouldStream) {
332
- res.setHeader('X-Stream', 'true');
333
- if (typeof res.flushHeaders === 'function') {
334
- res.flushHeaders();
335
- }
336
- }
337
- // Fetch content
338
- const startTime = Date.now();
339
- const result = await peel(url, options);
340
- const elapsed = Date.now() - startTime;
341
- // --- BM25 Schema Template Extraction (GET, no LLM needed) ---
342
- if (schema && typeof schema === 'string' && result.content) {
343
- const template = getSchemaTemplate(schema);
344
- if (template) {
345
- const { quickAnswer } = await import('../../core/quick-answer.js');
346
- const { smartExtractSchemaFields } = await import('../../core/schema-postprocess.js');
347
- const extracted = smartExtractSchemaFields(result.content, template.fields, quickAnswer, {
348
- pageTitle: result.title,
349
- pageUrl: result.url,
350
- metadata: result.metadata,
351
- });
352
- result.extracted = extracted;
353
- }
354
- }
355
- // Determine fetch type from the result method
356
- const fetchType = result.method === 'stealth' ? 'stealth' :
357
- result.method === 'browser' ? 'stealth' : 'basic';
358
- // Log request to database (PostgreSQL only)
359
- const pgStore = authStore;
360
- // Log usage for BOTH API key auth AND JWT session auth
361
- const logUserId = req.auth?.keyInfo?.accountId || req.user?.userId;
362
- if (logUserId && typeof pgStore.pool !== 'undefined') {
363
- pgStore.pool.query(`INSERT INTO usage_logs
364
- (user_id, endpoint, url, method, processing_time_ms, status_code, ip_address, user_agent, tokens_used)
365
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
366
- logUserId,
367
- 'fetch',
368
- url,
369
- fetchType,
370
- elapsed,
371
- 200,
372
- req.ip || req.socket.remoteAddress,
373
- req.get('user-agent'),
374
- result?.tokens || null,
375
- ]).catch((err) => {
376
- console.error('Failed to log request to usage_logs:', err);
377
- });
378
- }
379
- // Track usage (check for trackBurstUsage method to detect PostgresAuthStore)
380
- if (req.auth?.keyInfo?.key && typeof pgStore.trackBurstUsage === 'function') {
381
- // Track burst usage (always)
382
- await pgStore.trackBurstUsage(req.auth.keyInfo.key);
383
- // If soft-limited with extra usage available, charge to extra usage
384
- if (isSoftLimited && hasExtraUsage) {
385
- const extraResult = await pgStore.trackExtraUsage(req.auth.keyInfo.key, fetchType, url, elapsed, 200 // PeelResult doesn't include statusCode, assume success
386
- );
387
- if (extraResult.success) {
388
- res.setHeader('X-Extra-Usage-Charged', `$${extraResult.cost.toFixed(4)}`);
389
- res.setHeader('X-Extra-Usage-New-Balance', extraResult.newBalance.toFixed(2));
390
- }
391
- else {
392
- // Extra usage failed - fall back to soft limit
393
- res.setHeader('X-Degraded', 'Extra usage insufficient, degraded to soft limit');
394
- }
395
- }
396
- else if (!isSoftLimited) {
397
- // Normal weekly usage tracking
398
- await pgStore.trackUsage(req.auth.keyInfo.key, fetchType);
399
- }
400
- // If soft-limited WITHOUT extra usage, don't track (already over quota)
401
- }
402
- // Check usage alert (fire-and-forget, never block the response)
403
- if (req.auth?.keyInfo?.accountId && typeof pgStore.pool !== 'undefined') {
404
- try {
405
- const alertResult = await checkAndTriggerAlert(pgStore, req.auth.keyInfo.accountId);
406
- if (alertResult.shouldSendAlert && alertResult.usagePercent !== undefined) {
407
- await sendUsageAlertEmail({
408
- toEmail: alertResult.alertEmail || alertResult.userEmail,
409
- userName: alertResult.userName,
410
- usagePercent: alertResult.usagePercent,
411
- used: alertResult.used,
412
- total: alertResult.total,
413
- tier: alertResult.userTier,
414
- });
415
- // Mark alert as sent so we don't spam (rate-limited to once/week)
416
- await pgStore.pool.query('UPDATE users SET alert_sent_at = NOW() WHERE id = $1', [req.auth.keyInfo.accountId]);
417
- }
418
- }
419
- catch (alertErr) {
420
- // Never let alert errors affect the main response
421
- console.warn('[alert] Failed to check/send alert:', alertErr);
422
- }
423
- }
424
- // Cache result (unless storeInCache is explicitly false or cache bypass requested)
425
- if (storeInCache !== 'false' && !bypassCache) {
426
- cache.set(cacheKey, {
427
- result,
428
- timestamp: Date.now(),
429
- }, { ttl: cacheTtlMs });
430
- }
431
- // Apply ?detail=brief mode: truncate content and prepend TL;DR
432
- if (detailMode === 'brief' && result.content) {
433
- const words = result.content.split(/\s+/);
434
- const truncatedWords = words.slice(0, 500);
435
- const truncated = truncatedWords.join(' ');
436
- // Extract TL;DR from first non-empty paragraph
437
- const firstPara = result.content
438
- .split(/\n{2,}/)
439
- .map((p) => p.replace(/^#+\s*/, '').trim())
440
- .find((p) => p.length > 40 && !p.startsWith('!') && !p.startsWith('['));
441
- const tldr = firstPara
442
- ? firstPara.replace(/\s+/g, ' ').slice(0, 300) + (firstPara.length > 300 ? '...' : '')
443
- : truncated.slice(0, 200) + '...';
444
- result.content = `**TL;DR:** ${tldr}\n\n---\n\n${truncated}${words.length > 500 ? '\n\n*[Content truncated — use ?detail=full for complete output]*' : ''}`;
445
- const tokenEstimate = Math.round(truncatedWords.length * 0.75);
446
- res.setHeader('X-Detail-Mode', 'brief');
447
- res.setHeader('X-Token-Estimate', tokenEstimate.toString());
448
- }
449
- // --- question → answer field (GET) ---
450
- // When ?question= is provided, run quickAnswer() on the fetched content
451
- // and expose the result as an `answer` field in the response.
452
- const getAnswerResult = (question && typeof question === 'string' && result.content)
453
- ? quickAnswer({ question, content: result.content, url: result.url })
454
- : undefined;
455
- // --- summary field (GET) ---
456
- // When ?summary=true, return a truncated 500-word summary in a `summary` field.
457
- const getSummaryText = (summary === 'true' && result.content)
458
- ? extractSummary(result.content)
459
- : undefined;
460
- // Add usage headers (kept for backward compat; also surfaced in envelope metadata)
461
- res.setHeader('X-Cache', 'MISS');
462
- res.setHeader('X-Credits-Used', '1');
463
- res.setHeader('X-Processing-Time', elapsed.toString());
464
- res.setHeader('X-Fetch-Type', fetchType);
465
- // Build response — extend result with optional answer/summary fields
466
- const getResponseBody = { ...result };
467
- if (getAnswerResult !== undefined)
468
- getResponseBody.answer = getAnswerResult.answer;
469
- if (getSummaryText !== undefined)
470
- getResponseBody.summary = getSummaryText;
471
- if (wantsEnvelope(req)) {
472
- successResponse(res, getResponseBody, {
473
- requestId: req.requestId,
474
- processingTimeMs: elapsed,
475
- creditsUsed: 1,
476
- cached: false,
477
- fetchType,
478
- });
479
- }
480
- else {
481
- res.json(getResponseBody);
482
- }
483
- }
484
- catch (error) {
485
- const err = error;
486
- // Log error to database (PostgreSQL only)
487
- const pgStore = authStore;
488
- if (req.auth?.keyInfo?.accountId && typeof pgStore.pool !== 'undefined') {
489
- const url = req.query.url;
490
- const render = req.query.render === 'true';
491
- const fetchType = render ? 'stealth' : 'basic';
492
- pgStore.pool.query(`INSERT INTO usage_logs
493
- (user_id, endpoint, url, method, status_code, error, ip_address, user_agent, tokens_used)
494
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
495
- req.auth.keyInfo.accountId,
496
- 'fetch',
497
- url,
498
- fetchType,
499
- 500,
500
- err.message || 'Unknown error',
501
- req.ip || req.socket.remoteAddress,
502
- req.get('user-agent'),
503
- null,
504
- ]).catch((logErr) => {
505
- console.error('Failed to log error to usage_logs:', logErr);
506
- });
507
- }
508
- // SECURITY: Sanitize error messages to prevent information disclosure
509
- if (err.code) {
510
- // WebPeelError from core library - safe to expose with helpful context
511
- const safeMessage = err.message.replace(/[<>"']/g, ''); // Remove HTML chars
512
- const statusCode = err.code === 'TIMEOUT' ? 504
513
- : err.code === 'BLOCKED' ? 403
514
- : err.code === 'NETWORK' ? 502
515
- : 500;
516
- const hints = {
517
- TIMEOUT: 'Try increasing timeout with ?wait=10000, or use render=true for JS-heavy sites.',
518
- BLOCKED: 'This site blocks automated requests. Try adding render=true or use stealth mode (costs 5 credits).',
519
- NETWORK: 'Could not reach the target URL. Verify the URL is correct and the site is online.',
520
- };
521
- res.status(statusCode).json({
522
- success: false,
523
- error: {
524
- type: err.code,
525
- message: safeMessage,
526
- hint: hints[err.code] || undefined,
527
- docs: 'https://webpeel.dev/docs/api-reference#errors',
528
- },
529
- requestId: req.requestId,
530
- });
531
- }
532
- else {
533
- // Unexpected error - generic message only
534
- console.error('Fetch error:', err); // Log full error server-side
535
- res.status(500).json({
536
- success: false,
537
- error: {
538
- type: 'internal_error',
539
- message: 'An unexpected error occurred while fetching the URL. If this persists, check https://webpeel.dev/status',
540
- docs: 'https://webpeel.dev/docs/api-reference#errors',
541
- },
542
- requestId: req.requestId,
543
- });
544
- }
545
- }
546
- });
547
- // -----------------------------------------------------------------------
548
- // POST /v1/fetch — same as GET but accepts JSON body with extract param
549
- // POST /v2/scrape — alias with identical behaviour
550
- // -----------------------------------------------------------------------
551
- async function handlePostFetch(req, res) {
552
- try {
553
- // Require authentication — API key or JWT session
554
- const postUserId = req.auth?.keyInfo?.accountId || req.user?.userId;
555
- if (!postUserId) {
556
- res.status(401).json({
557
- error: 'unauthorized',
558
- message: 'API key required. Get one free at https://app.webpeel.dev/keys',
559
- });
560
- return;
561
- }
562
- const { url, render, wait, format, includeTags, excludeTags, images, location, languages, onlyMainContent, actions: rawActions, storeInCache: storeFlag,
563
- // Cache control
564
- noCache: noCacheBody, cacheTtl: cacheTtlBody,
565
- // Inline extraction (BYOK)
566
- extract, llmProvider, llmApiKey, llmModel,
567
- // Firecrawl-compatible formats array
568
- formats, stream,
569
- // Extended peel options
570
- budget, question, summary: summaryParam, readable, stealth, screenshot, maxTokens, selector, exclude, fullPage, raw, lite, timeout, proxies, chunk, device, viewportWidth, viewportHeight, waitUntil, waitSelector, blockResources, cloaked, schema: bodySchema, } = req.body;
571
- // --- Validate URL -------------------------------------------------------
572
- if (!url || typeof url !== 'string') {
573
- res.status(400).json({
574
- success: false,
575
- error: {
576
- type: 'invalid_request',
577
- message: 'Missing or invalid "url" in request body.',
578
- hint: 'Send JSON: { "url": "https://example.com" }',
579
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
580
- },
581
- requestId: req.requestId,
582
- });
583
- return;
584
- }
585
- if (url.length > 2048) {
586
- res.status(400).json({
587
- success: false,
588
- error: {
589
- type: 'invalid_url',
590
- message: 'URL too long (max 2048 characters)',
591
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
592
- },
593
- requestId: req.requestId,
594
- });
595
- return;
596
- }
597
- try {
598
- new URL(url);
599
- }
600
- catch {
601
- res.status(400).json({
602
- success: false,
603
- error: {
604
- type: 'invalid_url',
605
- message: 'Invalid URL format',
606
- hint: 'Ensure the URL includes a scheme (https://) and a valid hostname',
607
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
608
- },
609
- requestId: req.requestId,
610
- });
611
- return;
612
- }
613
- try {
614
- validateUrlForSSRF(url);
615
- }
616
- catch (error) {
617
- if (error instanceof SSRFError) {
618
- res.status(400).json({
619
- success: false,
620
- error: {
621
- type: 'forbidden_url',
622
- message: 'Cannot fetch localhost, private networks, or non-HTTP URLs',
623
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
624
- },
625
- requestId: req.requestId,
626
- });
627
- return;
628
- }
629
- throw error;
630
- }
631
- // --- Parse and normalize actions -----------------------------------------
632
- let postActions;
633
- if (rawActions !== undefined) {
634
- try {
635
- postActions = normalizeActions(rawActions);
636
- }
637
- catch (e) {
638
- res.status(400).json({
639
- success: false,
640
- error: {
641
- type: 'invalid_request',
642
- message: `Invalid "actions" parameter: ${e.message}`,
643
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
644
- },
645
- requestId: req.requestId,
646
- });
647
- return;
648
- }
649
- }
650
- // --- Cache bypass and lookup -------------------------------------------
651
- const postBypassCache = noCacheBody === true || req.headers['cache-control'] === 'no-cache';
652
- const postCacheTtlMs = typeof cacheTtlBody === 'number' ? cacheTtlBody * 1000 : 5 * 60 * 1000;
653
- const postActionsKey = postActions ? JSON.stringify(postActions) : '';
654
- const postCacheKey = `fetch:${url}:${render}:${wait}:${format}:${JSON.stringify(includeTags)}:${JSON.stringify(excludeTags)}:${images}:${location}:${JSON.stringify(languages)}:${onlyMainContent}:${stream}:${postActionsKey}:${budget}:${question}:${summaryParam}:${readable}:${stealth}:${screenshot}:${maxTokens}:${selector}:${JSON.stringify(exclude)}:${fullPage}:${raw}`;
655
- if (!postBypassCache && !extract) {
656
- const cached = cache.get(postCacheKey);
657
- if (cached) {
658
- const cacheAge = Date.now() - cached.timestamp;
659
- if (cacheAge < postCacheTtlMs) {
660
- res.setHeader('X-Cache', 'HIT');
661
- res.setHeader('X-Cache-Age', Math.floor(cacheAge / 1000).toString());
662
- if (wantsEnvelope(req)) {
663
- successResponse(res, cached.result, {
664
- requestId: req.requestId,
665
- cached: true,
666
- });
667
- }
668
- else {
669
- res.json(cached.result);
670
- }
671
- return;
672
- }
673
- }
674
- }
675
- // --- Resolve inline extract from body or Firecrawl-compatible formats ---
676
- let resolvedExtract = extract;
677
- if (!resolvedExtract && Array.isArray(formats)) {
678
- const jsonFormat = formats.find((f) => (typeof f === 'object' && f !== null && f.type === 'json') ||
679
- (typeof f === 'string' && f === 'json'));
680
- if (jsonFormat && typeof jsonFormat === 'object' && (jsonFormat.schema || jsonFormat.prompt)) {
681
- resolvedExtract = {
682
- schema: jsonFormat.schema,
683
- prompt: jsonFormat.prompt,
684
- };
685
- }
686
- }
687
- // Resolve schema template names (e.g. "product", "article") to field objects
688
- if (resolvedExtract && typeof resolvedExtract.schema === 'string') {
689
- const tmpl = getSchemaTemplate(resolvedExtract.schema);
690
- if (tmpl) {
691
- resolvedExtract = { ...resolvedExtract, schema: tmpl.fields };
692
- }
693
- else {
694
- // Try parsing as JSON string
695
- try {
696
- resolvedExtract = { ...resolvedExtract, schema: JSON.parse(resolvedExtract.schema) };
697
- }
698
- catch { /* leave as-is */ }
699
- }
700
- }
701
- // Validate LLM params if extraction is requested
702
- if (resolvedExtract && (resolvedExtract.schema || resolvedExtract.prompt)) {
703
- if (!llmProvider || !VALID_LLM_PROVIDERS.includes(llmProvider)) {
704
- res.status(400).json({
705
- success: false,
706
- error: {
707
- type: 'invalid_request',
708
- message: `"llmProvider" is required for inline extraction and must be one of: ${VALID_LLM_PROVIDERS.join(', ')}`,
709
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
710
- },
711
- requestId: req.requestId,
712
- });
713
- return;
714
- }
715
- if (!llmApiKey || typeof llmApiKey !== 'string' || llmApiKey.trim().length === 0) {
716
- res.status(400).json({
717
- success: false,
718
- error: {
719
- type: 'invalid_request',
720
- message: 'Missing or invalid "llmApiKey" (BYOK required for inline extraction)',
721
- hint: 'Pass your LLM provider API key in the "llmApiKey" field',
722
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
723
- },
724
- requestId: req.requestId,
725
- });
726
- return;
727
- }
728
- }
729
- // --- Build PeelOptions ---------------------------------------------------
730
- const isSoftLimited = req.auth?.softLimited === true;
731
- const hasExtraUsage = req.auth?.extraUsageAvailable === true;
732
- const includeTagsArray = Array.isArray(includeTags) ? includeTags : undefined;
733
- const excludeTagsArray = Array.isArray(excludeTags) ? excludeTags : undefined;
734
- const languagesArray = Array.isArray(languages) ? languages : undefined;
735
- const finalIncludeTags = onlyMainContent === true
736
- ? ['main', 'article', '.content', '#content']
737
- : includeTagsArray;
738
- const resolvedFormat = format || 'markdown';
739
- if (!['markdown', 'text', 'html', 'clean'].includes(resolvedFormat)) {
740
- res.status(400).json({
741
- success: false,
742
- error: {
743
- type: 'invalid_request',
744
- message: 'Invalid "format" parameter: must be "markdown", "text", "html", or "clean"',
745
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
746
- },
747
- requestId: req.requestId,
748
- });
749
- return;
750
- }
751
- const resolvedWait = typeof wait === 'number' ? wait : undefined;
752
- if (resolvedWait !== undefined && (isNaN(resolvedWait) || resolvedWait < 0 || resolvedWait > 60000)) {
753
- res.status(400).json({
754
- success: false,
755
- error: {
756
- type: 'invalid_request',
757
- message: 'Invalid "wait" parameter: must be between 0 and 60000ms',
758
- docs: 'https://webpeel.dev/docs/api-reference#fetch',
759
- },
760
- requestId: req.requestId,
761
- });
762
- return;
763
- }
764
- // When actions are present, force browser mode
765
- const postHasActions = postActions && postActions.length > 0;
766
- const postShouldRender = postHasActions || render === true;
767
- // Normalize exclude: accept string (comma-separated) or string array
768
- const excludeArray = exclude
769
- ? (Array.isArray(exclude) ? exclude : exclude.split(',').map(s => s.trim()).filter(Boolean))
770
- : undefined;
771
- const options = {
772
- render: (isSoftLimited && !hasExtraUsage && !postHasActions) ? false : postShouldRender,
773
- wait: (isSoftLimited && !hasExtraUsage) ? 0 : resolvedWait,
774
- format: resolvedFormat,
775
- stream: stream === true,
776
- includeTags: finalIncludeTags,
777
- excludeTags: excludeTagsArray,
778
- images: images === true,
779
- actions: postActions,
780
- location: location || languagesArray ? {
781
- country: location,
782
- languages: languagesArray,
783
- } : undefined,
784
- budget: typeof budget === 'number' ? budget : undefined,
785
- question: question,
786
- readable: readable === true,
787
- stealth: (isSoftLimited && !hasExtraUsage) ? false : stealth === true,
788
- screenshot: (isSoftLimited && !hasExtraUsage) ? false : screenshot === true,
789
- maxTokens: typeof maxTokens === 'number' ? maxTokens : undefined,
790
- selector: selector,
791
- exclude: excludeArray,
792
- fullPage: fullPage === true,
793
- raw: raw === true,
794
- lite: lite === true,
795
- timeout: typeof timeout === 'number' ? timeout : undefined,
796
- proxies: Array.isArray(proxies) ? proxies : undefined,
797
- device: device,
798
- viewportWidth: typeof viewportWidth === 'number' ? viewportWidth : undefined,
799
- viewportHeight: typeof viewportHeight === 'number' ? viewportHeight : undefined,
800
- waitUntil: waitUntil,
801
- waitSelector: waitSelector,
802
- blockResources: Array.isArray(blockResources) ? blockResources : undefined,
803
- };
804
- if (cloaked)
805
- options.cloaked = cloaked;
806
- if (chunk)
807
- options.chunk = chunk === true ? true : chunk;
808
- // Auto-budget: default to 4000 tokens for API requests when no budget specified
809
- // Opt-out: budget=0 explicitly disables. Lite mode disables auto-budget.
810
- if (options.budget === undefined && !options.lite) {
811
- options.budget = 4000;
812
- res.setHeader('X-Auto-Budget', '4000');
813
- }
814
- if (isSoftLimited && !hasExtraUsage && render === true && !postHasActions) {
815
- res.setHeader('X-Degraded', 'render=true downgraded to HTTP-only (quota exceeded)');
816
- }
817
- if (isSoftLimited && !hasExtraUsage && stealth === true) {
818
- res.setHeader('X-Degraded', 'stealth=true downgraded (quota exceeded)');
819
- }
820
- if (isSoftLimited && !hasExtraUsage && screenshot === true) {
821
- res.setHeader('X-Degraded', 'screenshot=true downgraded (quota exceeded)');
822
- }
823
- const shouldStream = options.stream === true;
824
- if (shouldStream) {
825
- res.setHeader('X-Stream', 'true');
826
- if (typeof res.flushHeaders === 'function') {
827
- res.flushHeaders();
828
- }
829
- }
830
- // --- Fetch content -------------------------------------------------------
831
- const startTime = Date.now();
832
- const result = await peel(url, options);
833
- const elapsed = Date.now() - startTime;
834
- // --- BM25 Schema Template Extraction (POST, no LLM needed) ---
835
- if (bodySchema && typeof bodySchema === 'string' && result.content) {
836
- const template = getSchemaTemplate(bodySchema);
837
- if (template) {
838
- const { quickAnswer } = await import('../../core/quick-answer.js');
839
- const { smartExtractSchemaFields } = await import('../../core/schema-postprocess.js');
840
- const extracted = smartExtractSchemaFields(result.content, template.fields, quickAnswer, {
841
- pageTitle: result.title,
842
- pageUrl: result.url,
843
- metadata: result.metadata,
844
- });
845
- result.extracted = extracted;
846
- }
847
- }
848
- // --- Inline extraction (post-fetch) -------------------------------------
849
- let jsonData;
850
- let extractTokensUsed;
851
- if (resolvedExtract && (resolvedExtract.schema || resolvedExtract.prompt) && llmApiKey) {
852
- const extractResult = await extractInlineJson(result.content, {
853
- schema: resolvedExtract.schema,
854
- prompt: resolvedExtract.prompt,
855
- llmProvider: llmProvider,
856
- llmApiKey: llmApiKey.trim(),
857
- llmModel,
858
- });
859
- jsonData = extractResult.data;
860
- extractTokensUsed = extractResult.tokensUsed;
861
- }
862
- // --- Usage tracking (same as GET) ----------------------------------------
863
- const fetchType = result.method === 'stealth' ? 'stealth' :
864
- result.method === 'browser' ? 'stealth' : 'basic';
865
- const pgStore = authStore;
866
- if (req.auth?.keyInfo?.accountId && typeof pgStore.pool !== 'undefined') {
867
- pgStore.pool.query(`INSERT INTO usage_logs
868
- (user_id, endpoint, url, method, processing_time_ms, status_code, ip_address, user_agent, tokens_used)
869
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
870
- req.auth.keyInfo.accountId,
871
- 'fetch',
872
- url,
873
- fetchType,
874
- elapsed,
875
- 200,
876
- req.ip || req.socket.remoteAddress,
877
- req.get('user-agent'),
878
- result?.tokens || null,
879
- ]).catch((err) => {
880
- console.error('Failed to log request to usage_logs:', err);
881
- });
882
- }
883
- if (req.auth?.keyInfo?.key && typeof pgStore.trackBurstUsage === 'function') {
884
- await pgStore.trackBurstUsage(req.auth.keyInfo.key);
885
- if (isSoftLimited && hasExtraUsage) {
886
- const extraResult = await pgStore.trackExtraUsage(req.auth.keyInfo.key, fetchType, url, elapsed, 200);
887
- if (extraResult.success) {
888
- res.setHeader('X-Extra-Usage-Charged', `$${extraResult.cost.toFixed(4)}`);
889
- res.setHeader('X-Extra-Usage-New-Balance', extraResult.newBalance.toFixed(2));
890
- }
891
- else {
892
- res.setHeader('X-Degraded', 'Extra usage insufficient, degraded to soft limit');
893
- }
894
- }
895
- else if (!isSoftLimited) {
896
- await pgStore.trackUsage(req.auth.keyInfo.key, fetchType);
897
- }
898
- }
899
- // Cache result (skip extraction results — they depend on user's LLM keys)
900
- if (storeFlag !== false && !postBypassCache && !resolvedExtract) {
901
- cache.set(postCacheKey, { result, timestamp: Date.now() }, { ttl: postCacheTtlMs });
902
- }
903
- // --- question → answer field (POST) ---
904
- // When question is provided, run quickAnswer() on the fetched content
905
- // and expose the result as an `answer` field in the response.
906
- const postAnswerResult = (question && typeof question === 'string' && result.content)
907
- ? quickAnswer({ question, content: result.content, url: result.url })
908
- : undefined;
909
- // --- summary field (POST) ---
910
- // When summary: true, return a truncated 500-word summary in a `summary` field.
911
- const postSummaryText = (summaryParam === true && result.content)
912
- ? extractSummary(result.content)
913
- : undefined;
914
- // --- Build response ------------------------------------------------------
915
- // Headers kept for backward compat; also surfaced in envelope metadata.
916
- res.setHeader('X-Cache', 'MISS');
917
- res.setHeader('X-Credits-Used', '1');
918
- res.setHeader('X-Processing-Time', elapsed.toString());
919
- res.setHeader('X-Fetch-Type', fetchType);
920
- const responseBody = { ...result };
921
- if (jsonData !== undefined) {
922
- responseBody.json = jsonData;
923
- }
924
- if (extractTokensUsed) {
925
- responseBody.extractTokensUsed = extractTokensUsed;
926
- }
927
- if (postAnswerResult !== undefined) {
928
- responseBody.answer = postAnswerResult.answer;
929
- }
930
- if (postSummaryText !== undefined) {
931
- responseBody.summary = postSummaryText;
932
- }
933
- if (wantsEnvelope(req)) {
934
- successResponse(res, responseBody, {
935
- requestId: req.requestId,
936
- processingTimeMs: elapsed,
937
- creditsUsed: 1,
938
- cached: false,
939
- fetchType,
940
- });
941
- }
942
- else {
943
- res.json(responseBody);
944
- }
945
- }
946
- catch (error) {
947
- const err = error;
948
- console.error('POST fetch/scrape error:', err);
949
- if (err.code) {
950
- const safeMessage = err.message.replace(/[<>"']/g, '');
951
- const statusCode = err.code === 'TIMEOUT' ? 504
952
- : err.code === 'BLOCKED' ? 403
953
- : err.code === 'NETWORK' ? 502
954
- : 500;
955
- const hints = {
956
- TIMEOUT: 'Try increasing timeout, or set render:true for JS-heavy sites.',
957
- BLOCKED: 'Site blocks automated requests. Try render:true or stealth mode.',
958
- NETWORK: 'Could not reach the target URL. Verify it is correct and online.',
959
- };
960
- res.status(statusCode).json({
961
- success: false,
962
- error: {
963
- type: err.code,
964
- message: safeMessage,
965
- hint: hints[err.code] || undefined,
966
- docs: 'https://webpeel.dev/docs/api-reference#errors',
967
- },
968
- requestId: req.requestId,
969
- });
970
- }
971
- else {
972
- res.status(500).json({
973
- success: false,
974
- error: {
975
- type: 'internal_error',
976
- message: 'An unexpected error occurred. If this persists, check https://webpeel.dev/status',
977
- docs: 'https://webpeel.dev/docs/api-reference#errors',
978
- },
979
- requestId: req.requestId,
980
- });
981
- }
982
- }
983
- }
984
- router.post('/v1/fetch', handlePostFetch);
985
- router.post('/v2/scrape', handlePostFetch);
986
- return router;
987
- }
988
- //# sourceMappingURL=fetch.js.map