@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,816 @@
1
+ /**
2
+ * Job application pipeline — stealth automated job applications.
3
+ * Uses the human behavior engine for natural interaction.
4
+ *
5
+ * Flow:
6
+ * 1. Rate-limit check (daily limit)
7
+ * 2. Launch persistent browser (preserves login cookies)
8
+ * 3. Warmup browse (optional, looks human)
9
+ * 4. Navigate to job posting naturally
10
+ * 5. Detect & click Apply button
11
+ * 6. Scan form fields & categorize
12
+ * 7. Fill fields with human behavior
13
+ * 8. Handle multi-step forms
14
+ * 9. Review / dry-run / auto submit
15
+ * 10. Log to ~/.webpeel/applications.json
16
+ */
17
+ import { join } from 'path';
18
+ import { homedir } from 'os';
19
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
20
+ import { randomUUID } from 'crypto';
21
+ import { chromium as stealthChromium } from 'playwright-extra';
22
+ import StealthPlugin from 'puppeteer-extra-plugin-stealth';
23
+ import { humanClick, humanClearAndType, humanScroll, humanDelay, humanRead, humanUploadFile, warmupBrowse, } from './human.js';
24
+ // Apply stealth plugin once (idempotent)
25
+ stealthChromium.use(StealthPlugin());
26
+ const WEBPEEL_DIR = join(homedir(), '.webpeel');
27
+ const APPLICATIONS_FILE = join(WEBPEEL_DIR, 'applications.json');
28
+ function ensureWebpeelDir() {
29
+ if (!existsSync(WEBPEEL_DIR)) {
30
+ mkdirSync(WEBPEEL_DIR, { recursive: true });
31
+ }
32
+ }
33
+ export function loadApplications() {
34
+ ensureWebpeelDir();
35
+ if (!existsSync(APPLICATIONS_FILE))
36
+ return [];
37
+ try {
38
+ const raw = readFileSync(APPLICATIONS_FILE, 'utf-8');
39
+ return JSON.parse(raw);
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ }
45
+ export function saveApplication(record) {
46
+ ensureWebpeelDir();
47
+ const existing = loadApplications();
48
+ existing.push(record);
49
+ writeFileSync(APPLICATIONS_FILE, JSON.stringify(existing, null, 2), 'utf-8');
50
+ }
51
+ export function getApplicationsToday() {
52
+ const apps = loadApplications();
53
+ const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
54
+ return apps.filter(a => a.appliedAt.startsWith(today)).length;
55
+ }
56
+ export function updateApplicationStatus(id, status) {
57
+ ensureWebpeelDir();
58
+ const apps = loadApplications();
59
+ const idx = apps.findIndex(a => a.id === id);
60
+ if (idx >= 0) {
61
+ apps[idx].status = status;
62
+ writeFileSync(APPLICATIONS_FILE, JSON.stringify(apps, null, 2), 'utf-8');
63
+ }
64
+ }
65
+ /**
66
+ * Categorize a field based on its label, type, name, and placeholder.
67
+ */
68
+ function categorizeField(field) {
69
+ const label = field.label.toLowerCase();
70
+ const placeholder = field.placeholder.toLowerCase();
71
+ const name = field.name.toLowerCase();
72
+ const id = field.id.toLowerCase();
73
+ const combined = `${label} ${placeholder} ${name} ${id}`;
74
+ // Input type shortcuts (most reliable signal)
75
+ if (field.type === 'file')
76
+ return 'resume';
77
+ if (field.type === 'email')
78
+ return 'email';
79
+ if (field.type === 'tel')
80
+ return 'phone';
81
+ // Label/name matching — more specific first
82
+ if (/\blinkedin\b/.test(combined))
83
+ return 'linkedin';
84
+ if (/\bwebsite\b|\bportfolio\b|\bpersonal\s+site\b/.test(combined))
85
+ return 'website';
86
+ if (/\bemail\b/.test(combined))
87
+ return 'email';
88
+ if (/\bphone\b|\bcell\b|\bmobile\b|\btelephone\b/.test(combined))
89
+ return 'phone';
90
+ if (/\bfull\s+name\b|\byour\s+name\b|\bfirst.*last\b/.test(combined))
91
+ return 'name';
92
+ if (/\bfirst\s+name\b/.test(combined))
93
+ return 'name';
94
+ if (/\blast\s+name\b|\bsurname\b/.test(combined))
95
+ return 'name';
96
+ if (/\bname\b/.test(combined))
97
+ return 'name';
98
+ if (/\blocation\b|\bcity\b|\bstate\b|\bzip\b|\bpostal\b|\baddress\b/.test(combined))
99
+ return 'location';
100
+ if (/\bwork\s+auth|\bauthoriz|\bsponsorship\b|\bvisa\b|\blegal\s+status\b/.test(combined))
101
+ return 'work-auth';
102
+ if (/\byears?\s+of\s+exp|\bexperience\b|\byears?\s+exp\b/.test(combined))
103
+ return 'experience';
104
+ if (/\bsalary\b|\bcompensation\b|\bpay\s+range\b|\bexpected\s+pay\b/.test(combined))
105
+ return 'salary';
106
+ if (/\beducation\b|\bdegree\b|\bschool\b|\buniversity\b|\bcollege\b/.test(combined))
107
+ return 'education';
108
+ if (/\bresume\b|\bcv\b/.test(combined))
109
+ return 'resume';
110
+ if (/\bcover\s+letter\b/.test(combined))
111
+ return 'cover-letter';
112
+ if (/\bskill\b/.test(combined))
113
+ return 'skills';
114
+ // Textarea with a meaningful label → likely a custom question
115
+ if (field.type === 'textarea' && label.length > 10)
116
+ return 'custom-question';
117
+ // Select dropdown with options + meaningful label → likely custom question
118
+ if (field.type === 'select' && field.options.length > 0 && label.length > 5)
119
+ return 'custom-question';
120
+ return 'unknown';
121
+ }
122
+ /**
123
+ * Scan a page (or modal) for form fields and categorize them.
124
+ * Uses page.evaluate() to access DOM elements.
125
+ *
126
+ * Note: all DOM API calls inside page.evaluate() use `any` since the project
127
+ * does not include the DOM lib (lib: ["ES2022"] only). The code is correct at
128
+ * runtime because it executes in the browser context.
129
+ */
130
+ async function detectFields(page) {
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ const rawFields = await page.evaluate(() => {
133
+ // All variables here are `any` — this runs inside the browser, not Node.js
134
+ const doc = globalThis.document; // eslint-disable-line
135
+ const elements = Array.from(doc.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea'));
136
+ return elements.map((el) => {
137
+ let label = '';
138
+ // Strategy 1: <label for="id">
139
+ if (el.id) {
140
+ const labelEl = doc.querySelector(`label[for="${el.id}"]`);
141
+ if (labelEl)
142
+ label = String(labelEl.textContent || '').trim();
143
+ }
144
+ // Strategy 2: parent <label>
145
+ if (!label) {
146
+ const parentLabel = el.closest('label');
147
+ if (parentLabel) {
148
+ label = String(parentLabel.textContent || '')
149
+ .replace(String(el.value || ''), '')
150
+ .trim();
151
+ }
152
+ }
153
+ // Strategy 3: aria-label
154
+ if (!label)
155
+ label = String(el.getAttribute('aria-label') || '');
156
+ // Strategy 4: aria-labelledby
157
+ if (!label) {
158
+ const labelledBy = el.getAttribute('aria-labelledby');
159
+ if (labelledBy) {
160
+ const labelEl = doc.getElementById(String(labelledBy));
161
+ if (labelEl)
162
+ label = String(labelEl.textContent || '').trim();
163
+ }
164
+ }
165
+ // Strategy 5: preceding sibling text
166
+ if (!label) {
167
+ const prev = el.previousElementSibling;
168
+ if (prev && !['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(String(prev.tagName))) {
169
+ label = String(prev.textContent || '').trim().slice(0, 100);
170
+ }
171
+ }
172
+ // Build unique CSS selector
173
+ let selector = '';
174
+ const elId = String(el.id || '');
175
+ const elName = String(el.name || '');
176
+ const tagName = String(el.tagName).toLowerCase();
177
+ if (elId) {
178
+ selector = `#${elId}`;
179
+ }
180
+ else if (elName) {
181
+ selector = `${tagName}[name="${elName}"]`;
182
+ }
183
+ else {
184
+ const form = el.closest('form, [role="dialog"], [class*="modal"]');
185
+ const container = form || doc.body;
186
+ const sibs = Array.from(container.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea'));
187
+ const idx = sibs.indexOf(el);
188
+ selector = `${tagName}:nth-child(${idx + 1})`;
189
+ }
190
+ // Collect <select> options
191
+ const options = [];
192
+ if (String(el.tagName) === 'SELECT') {
193
+ const optEls = Array.from(el.options || []);
194
+ for (const opt of optEls) {
195
+ if (opt.value && opt.text)
196
+ options.push(String(opt.text).trim());
197
+ }
198
+ }
199
+ const fieldType = String(el.tagName) === 'SELECT'
200
+ ? 'select'
201
+ : String(el.tagName) === 'TEXTAREA'
202
+ ? 'textarea'
203
+ : String(el.type || 'text');
204
+ return {
205
+ type: fieldType,
206
+ label: String(label).replace(/\s+/g, ' ').trim().slice(0, 150),
207
+ placeholder: String(el.placeholder || ''),
208
+ name: elName,
209
+ id: elId,
210
+ required: Boolean(el.hasAttribute('required')) ||
211
+ el.getAttribute('aria-required') === 'true',
212
+ options,
213
+ selector,
214
+ };
215
+ });
216
+ });
217
+ return rawFields.map(raw => ({
218
+ type: raw.type,
219
+ label: raw.label || raw.placeholder || raw.name || raw.id || '(unlabeled)',
220
+ selector: raw.selector,
221
+ options: raw.options.length > 0 ? raw.options : undefined,
222
+ required: raw.required,
223
+ category: categorizeField(raw),
224
+ }));
225
+ }
226
+ async function detectJobInfo(page) {
227
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
+ return page.evaluate(() => {
229
+ const doc = globalThis.document; // eslint-disable-line
230
+ const text = (sel) => String(doc.querySelector(sel)?.textContent || '').trim();
231
+ const title = text('.job-details-jobs-unified-top-card__job-title') ||
232
+ text('[data-testid="job-title"]') ||
233
+ text('h1.topcard__title') ||
234
+ text('h1') ||
235
+ String(doc.title || '').split('|')[0]?.trim() ||
236
+ '';
237
+ const company = text('.job-details-jobs-unified-top-card__company-name') ||
238
+ text('[data-testid="company-name"]') ||
239
+ text('.topcard__org-name-link');
240
+ const locationText = text('.job-details-jobs-unified-top-card__bullet') ||
241
+ text('[data-testid="job-location"]') ||
242
+ text('.topcard__flavor--bullet');
243
+ const salaryEl = doc.querySelector('[class*="salary"]') ||
244
+ doc.querySelector('[class*="compensation"]');
245
+ const salary = String(salaryEl?.textContent || '').trim() || undefined;
246
+ return {
247
+ title,
248
+ company,
249
+ location: locationText || undefined,
250
+ salary,
251
+ };
252
+ });
253
+ }
254
+ // ── LLM Integration ────────────────────────────────────────────────────
255
+ async function callLLMForAnswer(question, profile, jobTitle, company, fieldOptions, llmKey, llmProvider) {
256
+ const prompt = `You are filling out a job application. Answer this screening question concisely and professionally.
257
+
258
+ Job: ${jobTitle} at ${company}
259
+ Question: "${question}"
260
+ ${fieldOptions ? `Options: ${fieldOptions.join(', ')}` : ''}
261
+
262
+ Applicant profile:
263
+ - Name: ${profile.name}
264
+ - Title: ${profile.currentTitle}
265
+ - Experience: ${profile.yearsExperience} years
266
+ - Skills: ${profile.skills.join(', ')}
267
+ - Summary: ${profile.summary}
268
+
269
+ Answer (keep it concise, 1-3 sentences for text fields, or pick the best matching option for select/radio):`;
270
+ const systemPrompt = 'You are a helpful job application assistant. Provide concise, professional answers.';
271
+ if (llmProvider === 'anthropic') {
272
+ const resp = await fetch('https://api.anthropic.com/v1/messages', {
273
+ method: 'POST',
274
+ headers: {
275
+ 'Content-Type': 'application/json',
276
+ 'x-api-key': llmKey,
277
+ 'anthropic-version': '2023-06-01',
278
+ },
279
+ body: JSON.stringify({
280
+ model: 'claude-3-5-haiku-latest',
281
+ system: systemPrompt,
282
+ messages: [{ role: 'user', content: prompt }],
283
+ max_tokens: 500,
284
+ temperature: 0.3,
285
+ }),
286
+ });
287
+ if (!resp.ok)
288
+ throw new Error(`Anthropic API error: ${resp.status}`);
289
+ const json = await resp.json();
290
+ const blocks = Array.isArray(json?.content) ? json.content : [];
291
+ return blocks
292
+ .map(b => String(b?.text ?? ''))
293
+ .join('')
294
+ .trim();
295
+ }
296
+ else {
297
+ // Default: OpenAI
298
+ const resp = await fetch('https://api.openai.com/v1/chat/completions', {
299
+ method: 'POST',
300
+ headers: {
301
+ 'Content-Type': 'application/json',
302
+ Authorization: `Bearer ${llmKey}`,
303
+ },
304
+ body: JSON.stringify({
305
+ model: 'gpt-4o-mini',
306
+ messages: [
307
+ { role: 'system', content: systemPrompt },
308
+ { role: 'user', content: prompt },
309
+ ],
310
+ temperature: 0.3,
311
+ max_tokens: 300,
312
+ }),
313
+ });
314
+ if (!resp.ok)
315
+ throw new Error(`OpenAI API error: ${resp.status}`);
316
+ const json = await resp.json();
317
+ const choices = json?.choices;
318
+ const message = choices?.[0]?.message;
319
+ return String(message?.content ?? '').trim();
320
+ }
321
+ }
322
+ // ── Form Navigation ────────────────────────────────────────────────────
323
+ /** Find the "Next" / "Continue" / "Review" button in a multi-step form. */
324
+ async function findNextButton(page) {
325
+ const selectors = [
326
+ '[aria-label="Continue to next step"]',
327
+ '[aria-label="Next"]',
328
+ 'button:text("Next")',
329
+ 'button:text("Continue")',
330
+ 'button:text("Review")',
331
+ '[data-easy-apply-next-button]',
332
+ ];
333
+ for (const sel of selectors) {
334
+ try {
335
+ const el = await page.$(sel);
336
+ if (el && await el.isVisible())
337
+ return sel;
338
+ }
339
+ catch { /* continue */ }
340
+ }
341
+ // Text-based fallback
342
+ const btns = await page.$$('button');
343
+ for (const btn of btns) {
344
+ const text = (await btn.textContent() || '').trim().toLowerCase();
345
+ if ((text === 'next' || text === 'continue' || text === 'review') && await btn.isVisible()) {
346
+ // Generate a selector for this button
347
+ const id = await btn.getAttribute('id');
348
+ if (id)
349
+ return `#${id}`;
350
+ const cls = await btn.getAttribute('class');
351
+ if (cls)
352
+ return `button.${cls.split(' ')[0]}`;
353
+ }
354
+ }
355
+ return null;
356
+ }
357
+ /** Find the "Submit" / "Submit Application" button. */
358
+ async function findSubmitButton(page) {
359
+ const selectors = [
360
+ '[aria-label="Submit application"]',
361
+ 'button:text("Submit application")',
362
+ 'button:text("Submit Application")',
363
+ '[data-easy-apply-submit-button]',
364
+ ];
365
+ for (const sel of selectors) {
366
+ try {
367
+ const el = await page.$(sel);
368
+ if (el && await el.isVisible())
369
+ return sel;
370
+ }
371
+ catch { /* continue */ }
372
+ }
373
+ // Text-based fallback
374
+ const btns = await page.$$('button[type="submit"], button');
375
+ for (const btn of btns) {
376
+ const text = (await btn.textContent() || '').trim().toLowerCase();
377
+ if ((text.includes('submit') || text === 'apply') &&
378
+ !text.includes('next') &&
379
+ await btn.isVisible()) {
380
+ const id = await btn.getAttribute('id');
381
+ if (id)
382
+ return `#${id}`;
383
+ return 'button[type="submit"]';
384
+ }
385
+ }
386
+ return null;
387
+ }
388
+ /** Find and click the Apply button on a job posting. Returns the type of apply flow detected. */
389
+ async function clickApplyButton(page) {
390
+ // LinkedIn Easy Apply
391
+ const easyApplySelectors = [
392
+ '.jobs-apply-button',
393
+ '[aria-label="Easy Apply"]',
394
+ 'button:text("Easy Apply")',
395
+ ];
396
+ for (const sel of easyApplySelectors) {
397
+ try {
398
+ const el = await page.$(sel);
399
+ if (el && await el.isVisible()) {
400
+ await humanClick(page, sel);
401
+ await humanDelay(1000, 2500);
402
+ return 'easy-apply';
403
+ }
404
+ }
405
+ catch { /* continue */ }
406
+ }
407
+ // External apply button
408
+ const externalSelectors = [
409
+ '[data-testid="apply-button"]',
410
+ 'a:text("Apply")',
411
+ 'button:text("Apply")',
412
+ ];
413
+ for (const sel of externalSelectors) {
414
+ try {
415
+ const el = await page.$(sel);
416
+ if (el && await el.isVisible()) {
417
+ await humanClick(page, sel);
418
+ await humanDelay(1000, 2000);
419
+ return 'external';
420
+ }
421
+ }
422
+ catch { /* continue */ }
423
+ }
424
+ return 'not-found';
425
+ }
426
+ // ── Field Filling ──────────────────────────────────────────────────────
427
+ /** Get the value to fill for a field based on its category and the applicant's profile. */
428
+ async function getFieldValue(field, profile, jobTitle, company, llmKey, llmProvider) {
429
+ switch (field.category) {
430
+ case 'name': return profile.name;
431
+ case 'email': return profile.email;
432
+ case 'phone': return profile.phone;
433
+ case 'linkedin': return profile.linkedin ?? null;
434
+ case 'website': return profile.website ?? null;
435
+ case 'location': return profile.location;
436
+ case 'education': return profile.education;
437
+ case 'skills': return profile.skills.join(', ');
438
+ case 'experience': return String(profile.yearsExperience);
439
+ case 'resume': return null; // handled separately via file upload
440
+ case 'salary':
441
+ return profile.salaryRange ? String(profile.salaryRange.min) : null;
442
+ case 'work-auth': {
443
+ // Try to find matching option from the select's options list
444
+ if (field.options && field.options.length > 0) {
445
+ const target = profile.workAuthorization.toLowerCase();
446
+ const match = field.options.find(opt => opt.toLowerCase().includes(target) || target.includes(opt.toLowerCase().replace(/[^a-z\s]/g, '')));
447
+ return match ?? field.options[0] ?? profile.workAuthorization;
448
+ }
449
+ return profile.workAuthorization;
450
+ }
451
+ case 'cover-letter':
452
+ return [
453
+ profile.summary,
454
+ '',
455
+ `I am excited to apply for the ${jobTitle} position at ${company}. ` +
456
+ `With ${profile.yearsExperience} years of experience as ${profile.currentTitle}, ` +
457
+ `I am confident I would be a strong fit for this role.`,
458
+ ].join('\n');
459
+ case 'custom-question':
460
+ if (llmKey) {
461
+ try {
462
+ return await callLLMForAnswer(field.label, profile, jobTitle, company, field.options, llmKey, llmProvider ?? 'openai');
463
+ }
464
+ catch {
465
+ return null; // gracefully skip if LLM fails
466
+ }
467
+ }
468
+ return null;
469
+ case 'unknown':
470
+ default:
471
+ return null;
472
+ }
473
+ }
474
+ /** Fill a single form field using appropriate human behavior functions. */
475
+ async function fillField(page, field, value, warnings) {
476
+ try {
477
+ if (field.type === 'select') {
478
+ // Try by label text first, then by value
479
+ await page.selectOption(field.selector, { label: value }).catch(async () => {
480
+ await page.selectOption(field.selector, value).catch(() => { });
481
+ });
482
+ return true;
483
+ }
484
+ if (field.type === 'file') {
485
+ await humanUploadFile(page, field.selector, value);
486
+ return true;
487
+ }
488
+ if (field.type === 'radio') {
489
+ // Try clicking a radio with matching value
490
+ const radioSel = `input[type="radio"][value="${value}"]`;
491
+ const el = await page.$(radioSel);
492
+ if (el && await el.isVisible()) {
493
+ await humanClick(page, radioSel);
494
+ return true;
495
+ }
496
+ // Try by label text
497
+ const label = await page.$(`label:text("${value}")`);
498
+ if (label) {
499
+ const box = await label.boundingBox();
500
+ if (box) {
501
+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
502
+ return true;
503
+ }
504
+ }
505
+ return false;
506
+ }
507
+ if (field.type === 'checkbox') {
508
+ const lower = value.toLowerCase();
509
+ if (lower === 'true' || lower === 'yes' || lower === '1') {
510
+ const el = await page.$(field.selector);
511
+ if (el && !(await el.isChecked())) {
512
+ await humanClick(page, field.selector);
513
+ }
514
+ return true;
515
+ }
516
+ return false;
517
+ }
518
+ // Text, textarea, email, tel — use clear-and-type for reliability
519
+ await humanClearAndType(page, field.selector, value);
520
+ return true;
521
+ }
522
+ catch (err) {
523
+ warnings.push(`Failed to fill "${field.label}": ${err instanceof Error ? err.message : String(err)}`);
524
+ return false;
525
+ }
526
+ }
527
+ // ── Main Entry Point ───────────────────────────────────────────────────
528
+ /**
529
+ * Apply to a job with stealth human-like behavior.
530
+ *
531
+ * Default mode is 'review' — it fills the form and waits for your approval
532
+ * before submitting. Use 'auto' for fully automated, 'dry-run' to see what
533
+ * would be filled without actually clicking submit.
534
+ *
535
+ * Requires a persistent browser session for login state preservation.
536
+ * On first run, the browser will open — log into LinkedIn, then the session
537
+ * is saved to `~/.webpeel/sessions/linkedin/` for future runs.
538
+ *
539
+ * @example
540
+ * ```typescript
541
+ * const result = await applyToJob({
542
+ * url: 'https://linkedin.com/jobs/view/...',
543
+ * profile: myProfile,
544
+ * mode: 'review',
545
+ * });
546
+ * ```
547
+ */
548
+ export async function applyToJob(options) {
549
+ const startTime = Date.now();
550
+ const { url, profile, mode = 'review', llmKey, llmProvider = 'openai', dailyLimit = 8, timeout = 120_000, warmup: doWarmup = true, warmupDuration, onProgress, } = options;
551
+ const progress = (stage, message, extra) => {
552
+ onProgress?.({ stage, message, ...extra });
553
+ };
554
+ const result = {
555
+ submitted: false,
556
+ job: { title: '', company: '' },
557
+ fieldsFilled: 0,
558
+ llmAnswers: 0,
559
+ fieldsSkipped: [],
560
+ warnings: [],
561
+ elapsed: 0,
562
+ };
563
+ // ── 1. Rate limit check ────────────────────────────────────────────
564
+ if (mode !== 'dry-run') {
565
+ const todayCount = getApplicationsToday();
566
+ if (todayCount >= dailyLimit) {
567
+ const msg = `Daily application limit reached (${todayCount}/${dailyLimit}). Try again tomorrow.`;
568
+ result.error = msg;
569
+ result.elapsed = Date.now() - startTime;
570
+ progress('error', msg);
571
+ return result;
572
+ }
573
+ }
574
+ // ── 2. Determine session directory ────────────────────────────────
575
+ const isLinkedIn = url.includes('linkedin.com');
576
+ const siteName = isLinkedIn
577
+ ? 'linkedin'
578
+ : (() => {
579
+ try {
580
+ return new URL(url).hostname.replace('www.', '').split('.')[0] ?? 'generic';
581
+ }
582
+ catch {
583
+ return 'generic';
584
+ }
585
+ })();
586
+ const sessionDir = options.sessionDir ?? join(homedir(), '.webpeel', 'sessions', siteName);
587
+ mkdirSync(sessionDir, { recursive: true });
588
+ let context = null;
589
+ let page = null;
590
+ try {
591
+ // ── 3. Launch persistent browser ──────────────────────────────
592
+ progress('navigating', 'Launching browser with persistent session...');
593
+ context = await stealthChromium.launchPersistentContext(sessionDir, {
594
+ headless: false, // visible so user can monitor (or log in on first run)
595
+ viewport: { width: 1440, height: 900 },
596
+ locale: 'en-US',
597
+ timezoneId: 'America/New_York',
598
+ args: [
599
+ '--no-sandbox',
600
+ '--disable-setuid-sandbox',
601
+ '--disable-blink-features=AutomationControlled',
602
+ ],
603
+ });
604
+ // context is guaranteed non-null here — assignment above throws on failure
605
+ const existingPages = context.pages();
606
+ page = existingPages.length > 0 ? existingPages[0] : await context.newPage();
607
+ // ── 4. Warmup phase ───────────────────────────────────────────
608
+ if (doWarmup) {
609
+ progress('warmup', 'Warming up with natural browsing...');
610
+ try {
611
+ const warmupUrl = isLinkedIn
612
+ ? 'https://www.linkedin.com/feed/'
613
+ : new URL(url).origin;
614
+ await page.goto(warmupUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
615
+ await humanDelay(2000, 4000);
616
+ const warmupMs = warmupDuration ?? Math.round(15000 + Math.random() * 15000);
617
+ await warmupBrowse(page, warmupMs);
618
+ }
619
+ catch {
620
+ result.warnings.push('Warmup phase failed — continuing without warmup');
621
+ }
622
+ }
623
+ // ── 5. Navigate to job posting ────────────────────────────────
624
+ progress('navigating', `Navigating to job posting...`);
625
+ // Navigate via jobs home first (looks more natural than direct URL jump)
626
+ if (isLinkedIn) {
627
+ try {
628
+ await page.goto('https://www.linkedin.com/jobs/', {
629
+ waitUntil: 'domcontentloaded',
630
+ timeout: 30000,
631
+ });
632
+ await humanDelay(1500, 3000);
633
+ await humanScroll(page, { direction: 'down', amount: 200 });
634
+ await humanDelay(800, 1500);
635
+ }
636
+ catch { /* ignore, proceed to actual URL */ }
637
+ }
638
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout });
639
+ await humanDelay(1500, 3000);
640
+ // ── 6. Read the job posting ───────────────────────────────────
641
+ progress('reading', 'Reading job posting...');
642
+ await humanRead(page, Math.round(4000 + Math.random() * 6000)); // 4-10s
643
+ // Extract job info from the page
644
+ result.job = await detectJobInfo(page);
645
+ // ── 7. Click Apply button ─────────────────────────────────────
646
+ progress('navigating', 'Clicking Apply button...');
647
+ const applyType = await clickApplyButton(page);
648
+ if (applyType === 'not-found') {
649
+ result.warnings.push('Could not find Apply button — the form may be directly on the page');
650
+ }
651
+ else {
652
+ result.warnings.push(`Apply type: ${applyType}`);
653
+ }
654
+ await humanDelay(1500, 3000);
655
+ // ── 8. Multi-step form filling ────────────────────────────────
656
+ progress('filling', 'Scanning and filling form...');
657
+ let stepCount = 0;
658
+ const MAX_STEPS = 10;
659
+ const allAnswers = {};
660
+ while (stepCount < MAX_STEPS) {
661
+ stepCount++;
662
+ const fields = await detectFields(page);
663
+ progress('filling', `Step ${stepCount}: found ${fields.length} field(s)`, { fields });
664
+ for (const field of fields) {
665
+ if (field.category === 'unknown' && field.type !== 'select') {
666
+ result.fieldsSkipped.push(field.label);
667
+ continue;
668
+ }
669
+ await humanDelay(300, 800);
670
+ // Resume file upload — special handling
671
+ if (field.category === 'resume' && field.type === 'file') {
672
+ try {
673
+ await humanUploadFile(page, field.selector, profile.resumePath);
674
+ result.fieldsFilled++;
675
+ allAnswers[field.label] = `[File: ${profile.resumePath}]`;
676
+ }
677
+ catch (err) {
678
+ result.warnings.push(`Resume upload failed: ${err instanceof Error ? err.message : String(err)}`);
679
+ }
680
+ continue;
681
+ }
682
+ const value = await getFieldValue(field, profile, result.job.title, result.job.company, llmKey, llmProvider);
683
+ if (value === null) {
684
+ result.fieldsSkipped.push(field.label);
685
+ continue;
686
+ }
687
+ if (field.category === 'custom-question' && llmKey) {
688
+ result.llmAnswers++;
689
+ }
690
+ const filled = await fillField(page, field, value, result.warnings);
691
+ if (filled) {
692
+ result.fieldsFilled++;
693
+ allAnswers[field.label] = value.length > 80 ? value.slice(0, 77) + '...' : value;
694
+ }
695
+ else {
696
+ result.fieldsSkipped.push(field.label);
697
+ }
698
+ await humanDelay(500, 1500);
699
+ }
700
+ // Check for submit button
701
+ const submitBtn = await findSubmitButton(page);
702
+ if (submitBtn) {
703
+ progress('reviewing', 'Form complete — ready to submit', { answers: allAnswers });
704
+ if (mode === 'dry-run') {
705
+ console.log('\nšŸ“‹ DRY-RUN — fields that would be filled:');
706
+ for (const [label, value] of Object.entries(allAnswers)) {
707
+ console.log(` āœ“ ${label}: ${value}`);
708
+ }
709
+ if (result.fieldsSkipped.length > 0) {
710
+ console.log(`\n āš ļø Skipped: ${result.fieldsSkipped.join(', ')}`);
711
+ }
712
+ console.log('\n[Dry-run complete — NOT submitted]\n');
713
+ result.submitted = false;
714
+ break;
715
+ }
716
+ if (mode === 'review') {
717
+ console.log('\nšŸ“‹ Review — fields filled:');
718
+ for (const [label, value] of Object.entries(allAnswers)) {
719
+ console.log(` āœ“ ${label}: ${value}`);
720
+ }
721
+ if (result.fieldsSkipped.length > 0) {
722
+ console.log(`\n āš ļø Skipped: ${result.fieldsSkipped.join(', ')}`);
723
+ }
724
+ console.log('\nPlease review the form in the browser window.');
725
+ console.log('Press Enter to submit, or Ctrl+C to abort...');
726
+ await new Promise(resolve => {
727
+ const onData = (_data) => {
728
+ process.stdin.removeListener('data', onData);
729
+ try {
730
+ process.stdin.setRawMode(false);
731
+ }
732
+ catch { /* not a TTY */ }
733
+ process.stdin.pause();
734
+ resolve();
735
+ };
736
+ try {
737
+ process.stdin.setRawMode(true);
738
+ }
739
+ catch { /* not a TTY */ }
740
+ process.stdin.resume();
741
+ process.stdin.once('data', onData);
742
+ });
743
+ }
744
+ // ── 9. Submit ──────────────────────────────────────────────
745
+ progress('submitting', 'Submitting application...');
746
+ await humanClick(page, submitBtn);
747
+ await humanDelay(2000, 4000);
748
+ // Check for success confirmation text
749
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
750
+ const submitted = await page.evaluate(() => {
751
+ const doc = globalThis.document; // eslint-disable-line
752
+ const body = String(doc.body?.textContent || '');
753
+ return (body.includes('Application submitted') ||
754
+ body.includes('application was sent') ||
755
+ body.includes('successfully applied') ||
756
+ body.includes('Thank you for applying') ||
757
+ doc.querySelector('[class*="post-apply"]') !== null ||
758
+ doc.querySelector('[aria-label*="Application submitted"]') !== null);
759
+ });
760
+ result.submitted = submitted;
761
+ if (!submitted) {
762
+ result.warnings.push('Could not confirm submission — check the browser window');
763
+ }
764
+ break;
765
+ }
766
+ // Look for Next/Continue button to advance to the next step
767
+ const nextBtn = await findNextButton(page);
768
+ if (!nextBtn) {
769
+ result.warnings.push('No Next or Submit button found — stopping form traversal');
770
+ break;
771
+ }
772
+ await humanClick(page, nextBtn);
773
+ await humanDelay(1500, 3000);
774
+ }
775
+ if (stepCount >= MAX_STEPS) {
776
+ result.warnings.push(`Reached form step limit (${MAX_STEPS}) — form may be unusually long`);
777
+ }
778
+ // ── 10. Log application ───────────────────────────────────────
779
+ if (mode !== 'dry-run') {
780
+ const record = {
781
+ id: randomUUID(),
782
+ url,
783
+ company: result.job.company,
784
+ title: result.job.title,
785
+ location: result.job.location,
786
+ salary: result.job.salary,
787
+ appliedAt: new Date().toISOString(),
788
+ mode,
789
+ status: 'applied',
790
+ fieldsFilled: result.fieldsFilled,
791
+ fieldsSkipped: result.fieldsSkipped,
792
+ warnings: result.warnings,
793
+ };
794
+ saveApplication(record);
795
+ }
796
+ progress('done', result.submitted
797
+ ? 'āœ… Application submitted!'
798
+ : mode === 'dry-run'
799
+ ? 'šŸ“‹ Dry-run complete (not submitted)'
800
+ : 'šŸ“‹ Application completed');
801
+ }
802
+ catch (err) {
803
+ const msg = err instanceof Error ? err.message : String(err);
804
+ result.error = msg;
805
+ progress('error', `Error: ${msg}`);
806
+ }
807
+ finally {
808
+ // Close page but keep context alive (preserves session for next run)
809
+ if (page && !page.isClosed()) {
810
+ await page.close().catch(() => { });
811
+ }
812
+ // DO NOT close context — this keeps the session/cookies alive
813
+ }
814
+ result.elapsed = Date.now() - startTime;
815
+ return result;
816
+ }