notelm-mcp 1.2.1

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 (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +456 -0
  3. package/dist/auth/auth-manager.d.ts +139 -0
  4. package/dist/auth/auth-manager.d.ts.map +1 -0
  5. package/dist/auth/auth-manager.js +960 -0
  6. package/dist/auth/auth-manager.js.map +1 -0
  7. package/dist/config.d.ts +92 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +219 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/constants.d.ts +58 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +133 -0
  14. package/dist/constants.js.map +1 -0
  15. package/dist/errors.d.ts +26 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +41 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/index.d.ts +32 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +325 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/library/notebook-library.d.ts +70 -0
  24. package/dist/library/notebook-library.d.ts.map +1 -0
  25. package/dist/library/notebook-library.js +279 -0
  26. package/dist/library/notebook-library.js.map +1 -0
  27. package/dist/library/types.d.ts +67 -0
  28. package/dist/library/types.d.ts.map +1 -0
  29. package/dist/library/types.js +8 -0
  30. package/dist/library/types.js.map +1 -0
  31. package/dist/playwright.config.d.ts +3 -0
  32. package/dist/playwright.config.d.ts.map +1 -0
  33. package/dist/playwright.config.js +38 -0
  34. package/dist/playwright.config.js.map +1 -0
  35. package/dist/resources/resource-handlers.d.ts +22 -0
  36. package/dist/resources/resource-handlers.d.ts.map +1 -0
  37. package/dist/resources/resource-handlers.js +216 -0
  38. package/dist/resources/resource-handlers.js.map +1 -0
  39. package/dist/scripts/save-auth-state.d.ts +2 -0
  40. package/dist/scripts/save-auth-state.d.ts.map +1 -0
  41. package/dist/scripts/save-auth-state.js +91 -0
  42. package/dist/scripts/save-auth-state.js.map +1 -0
  43. package/dist/session/browser-session.d.ts +108 -0
  44. package/dist/session/browser-session.d.ts.map +1 -0
  45. package/dist/session/browser-session.js +636 -0
  46. package/dist/session/browser-session.js.map +1 -0
  47. package/dist/session/session-manager.d.ts +76 -0
  48. package/dist/session/session-manager.d.ts.map +1 -0
  49. package/dist/session/session-manager.js +273 -0
  50. package/dist/session/session-manager.js.map +1 -0
  51. package/dist/session/shared-context-manager.d.ts +107 -0
  52. package/dist/session/shared-context-manager.d.ts.map +1 -0
  53. package/dist/session/shared-context-manager.js +447 -0
  54. package/dist/session/shared-context-manager.js.map +1 -0
  55. package/dist/src/auth/auth-manager.d.ts +139 -0
  56. package/dist/src/auth/auth-manager.d.ts.map +1 -0
  57. package/dist/src/auth/auth-manager.js +960 -0
  58. package/dist/src/auth/auth-manager.js.map +1 -0
  59. package/dist/src/config.d.ts +92 -0
  60. package/dist/src/config.d.ts.map +1 -0
  61. package/dist/src/config.js +219 -0
  62. package/dist/src/config.js.map +1 -0
  63. package/dist/src/constants.d.ts +58 -0
  64. package/dist/src/constants.d.ts.map +1 -0
  65. package/dist/src/constants.js +133 -0
  66. package/dist/src/constants.js.map +1 -0
  67. package/dist/src/errors.d.ts +26 -0
  68. package/dist/src/errors.d.ts.map +1 -0
  69. package/dist/src/errors.js +41 -0
  70. package/dist/src/errors.js.map +1 -0
  71. package/dist/src/index.d.ts +32 -0
  72. package/dist/src/index.d.ts.map +1 -0
  73. package/dist/src/index.js +325 -0
  74. package/dist/src/index.js.map +1 -0
  75. package/dist/src/library/notebook-library.d.ts +70 -0
  76. package/dist/src/library/notebook-library.d.ts.map +1 -0
  77. package/dist/src/library/notebook-library.js +279 -0
  78. package/dist/src/library/notebook-library.js.map +1 -0
  79. package/dist/src/library/types.d.ts +67 -0
  80. package/dist/src/library/types.d.ts.map +1 -0
  81. package/dist/src/library/types.js +8 -0
  82. package/dist/src/library/types.js.map +1 -0
  83. package/dist/src/resources/resource-handlers.d.ts +22 -0
  84. package/dist/src/resources/resource-handlers.d.ts.map +1 -0
  85. package/dist/src/resources/resource-handlers.js +216 -0
  86. package/dist/src/resources/resource-handlers.js.map +1 -0
  87. package/dist/src/scripts/health-check.d.ts +13 -0
  88. package/dist/src/scripts/health-check.d.ts.map +1 -0
  89. package/dist/src/scripts/health-check.js +100 -0
  90. package/dist/src/scripts/health-check.js.map +1 -0
  91. package/dist/src/session/browser-session.d.ts +108 -0
  92. package/dist/src/session/browser-session.d.ts.map +1 -0
  93. package/dist/src/session/browser-session.js +642 -0
  94. package/dist/src/session/browser-session.js.map +1 -0
  95. package/dist/src/session/session-manager.d.ts +76 -0
  96. package/dist/src/session/session-manager.d.ts.map +1 -0
  97. package/dist/src/session/session-manager.js +273 -0
  98. package/dist/src/session/session-manager.js.map +1 -0
  99. package/dist/src/session/shared-context-manager.d.ts +107 -0
  100. package/dist/src/session/shared-context-manager.d.ts.map +1 -0
  101. package/dist/src/session/shared-context-manager.js +447 -0
  102. package/dist/src/session/shared-context-manager.js.map +1 -0
  103. package/dist/src/tools/definitions/ask-question.d.ts +8 -0
  104. package/dist/src/tools/definitions/ask-question.d.ts.map +1 -0
  105. package/dist/src/tools/definitions/ask-question.js +211 -0
  106. package/dist/src/tools/definitions/ask-question.js.map +1 -0
  107. package/dist/src/tools/definitions/notebook-management.d.ts +3 -0
  108. package/dist/src/tools/definitions/notebook-management.d.ts.map +1 -0
  109. package/dist/src/tools/definitions/notebook-management.js +243 -0
  110. package/dist/src/tools/definitions/notebook-management.js.map +1 -0
  111. package/dist/src/tools/definitions/session-management.d.ts +3 -0
  112. package/dist/src/tools/definitions/session-management.d.ts.map +1 -0
  113. package/dist/src/tools/definitions/session-management.js +41 -0
  114. package/dist/src/tools/definitions/session-management.js.map +1 -0
  115. package/dist/src/tools/definitions/system.d.ts +3 -0
  116. package/dist/src/tools/definitions/system.d.ts.map +1 -0
  117. package/dist/src/tools/definitions/system.js +143 -0
  118. package/dist/src/tools/definitions/system.js.map +1 -0
  119. package/dist/src/tools/definitions.d.ts +12 -0
  120. package/dist/src/tools/definitions.d.ts.map +1 -0
  121. package/dist/src/tools/definitions.js +26 -0
  122. package/dist/src/tools/definitions.js.map +1 -0
  123. package/dist/src/tools/handlers.d.ts +212 -0
  124. package/dist/src/tools/handlers.d.ts.map +1 -0
  125. package/dist/src/tools/handlers.js +712 -0
  126. package/dist/src/tools/handlers.js.map +1 -0
  127. package/dist/src/tools/index.d.ts +8 -0
  128. package/dist/src/tools/index.d.ts.map +1 -0
  129. package/dist/src/tools/index.js +8 -0
  130. package/dist/src/tools/index.js.map +1 -0
  131. package/dist/src/types.d.ts +88 -0
  132. package/dist/src/types.d.ts.map +1 -0
  133. package/dist/src/types.js +5 -0
  134. package/dist/src/types.js.map +1 -0
  135. package/dist/src/utils/auth-manager.d.ts +2 -0
  136. package/dist/src/utils/auth-manager.d.ts.map +1 -0
  137. package/dist/src/utils/auth-manager.js +25 -0
  138. package/dist/src/utils/auth-manager.js.map +1 -0
  139. package/dist/src/utils/cleanup-manager.d.ts +133 -0
  140. package/dist/src/utils/cleanup-manager.d.ts.map +1 -0
  141. package/dist/src/utils/cleanup-manager.js +673 -0
  142. package/dist/src/utils/cleanup-manager.js.map +1 -0
  143. package/dist/src/utils/cli-handler.d.ts +16 -0
  144. package/dist/src/utils/cli-handler.d.ts.map +1 -0
  145. package/dist/src/utils/cli-handler.js +102 -0
  146. package/dist/src/utils/cli-handler.js.map +1 -0
  147. package/dist/src/utils/logger.d.ts +61 -0
  148. package/dist/src/utils/logger.d.ts.map +1 -0
  149. package/dist/src/utils/logger.js +92 -0
  150. package/dist/src/utils/logger.js.map +1 -0
  151. package/dist/src/utils/page-utils.d.ts +54 -0
  152. package/dist/src/utils/page-utils.d.ts.map +1 -0
  153. package/dist/src/utils/page-utils.js +381 -0
  154. package/dist/src/utils/page-utils.js.map +1 -0
  155. package/dist/src/utils/rate-limit-handler.d.ts +42 -0
  156. package/dist/src/utils/rate-limit-handler.d.ts.map +1 -0
  157. package/dist/src/utils/rate-limit-handler.js +88 -0
  158. package/dist/src/utils/rate-limit-handler.js.map +1 -0
  159. package/dist/src/utils/rate-limit-handler.test.d.ts +7 -0
  160. package/dist/src/utils/rate-limit-handler.test.d.ts.map +1 -0
  161. package/dist/src/utils/rate-limit-handler.test.js +86 -0
  162. package/dist/src/utils/rate-limit-handler.test.js.map +1 -0
  163. package/dist/src/utils/settings-manager.d.ts +37 -0
  164. package/dist/src/utils/settings-manager.d.ts.map +1 -0
  165. package/dist/src/utils/settings-manager.js +121 -0
  166. package/dist/src/utils/settings-manager.js.map +1 -0
  167. package/dist/src/utils/stealth-utils.d.ts +135 -0
  168. package/dist/src/utils/stealth-utils.d.ts.map +1 -0
  169. package/dist/src/utils/stealth-utils.js +396 -0
  170. package/dist/src/utils/stealth-utils.js.map +1 -0
  171. package/dist/src/utils/stealth-utils.test.d.ts +7 -0
  172. package/dist/src/utils/stealth-utils.test.d.ts.map +1 -0
  173. package/dist/src/utils/stealth-utils.test.js +72 -0
  174. package/dist/src/utils/stealth-utils.test.js.map +1 -0
  175. package/dist/tests/e2e/authenticated.spec.d.ts +2 -0
  176. package/dist/tests/e2e/authenticated.spec.d.ts.map +1 -0
  177. package/dist/tests/e2e/authenticated.spec.js +41 -0
  178. package/dist/tests/e2e/authenticated.spec.js.map +1 -0
  179. package/dist/tests/e2e/mocked.spec.d.ts +2 -0
  180. package/dist/tests/e2e/mocked.spec.d.ts.map +1 -0
  181. package/dist/tests/e2e/mocked.spec.js +32 -0
  182. package/dist/tests/e2e/mocked.spec.js.map +1 -0
  183. package/dist/tests/mocks/handlers.d.ts +4 -0
  184. package/dist/tests/mocks/handlers.d.ts.map +1 -0
  185. package/dist/tests/mocks/handlers.js +55 -0
  186. package/dist/tests/mocks/handlers.js.map +1 -0
  187. package/dist/tests/mocks/setup.d.ts +3 -0
  188. package/dist/tests/mocks/setup.d.ts.map +1 -0
  189. package/dist/tests/mocks/setup.js +77 -0
  190. package/dist/tests/mocks/setup.js.map +1 -0
  191. package/dist/tools/definitions/ask-question.d.ts +8 -0
  192. package/dist/tools/definitions/ask-question.d.ts.map +1 -0
  193. package/dist/tools/definitions/ask-question.js +211 -0
  194. package/dist/tools/definitions/ask-question.js.map +1 -0
  195. package/dist/tools/definitions/notebook-management.d.ts +3 -0
  196. package/dist/tools/definitions/notebook-management.d.ts.map +1 -0
  197. package/dist/tools/definitions/notebook-management.js +243 -0
  198. package/dist/tools/definitions/notebook-management.js.map +1 -0
  199. package/dist/tools/definitions/session-management.d.ts +3 -0
  200. package/dist/tools/definitions/session-management.d.ts.map +1 -0
  201. package/dist/tools/definitions/session-management.js +41 -0
  202. package/dist/tools/definitions/session-management.js.map +1 -0
  203. package/dist/tools/definitions/system.d.ts +3 -0
  204. package/dist/tools/definitions/system.d.ts.map +1 -0
  205. package/dist/tools/definitions/system.js +143 -0
  206. package/dist/tools/definitions/system.js.map +1 -0
  207. package/dist/tools/definitions.d.ts +12 -0
  208. package/dist/tools/definitions.d.ts.map +1 -0
  209. package/dist/tools/definitions.js +26 -0
  210. package/dist/tools/definitions.js.map +1 -0
  211. package/dist/tools/handlers.d.ts +212 -0
  212. package/dist/tools/handlers.d.ts.map +1 -0
  213. package/dist/tools/handlers.js +712 -0
  214. package/dist/tools/handlers.js.map +1 -0
  215. package/dist/tools/index.d.ts +8 -0
  216. package/dist/tools/index.d.ts.map +1 -0
  217. package/dist/tools/index.js +8 -0
  218. package/dist/tools/index.js.map +1 -0
  219. package/dist/types.d.ts +82 -0
  220. package/dist/types.d.ts.map +1 -0
  221. package/dist/types.js +5 -0
  222. package/dist/types.js.map +1 -0
  223. package/dist/utils/cleanup-manager.d.ts +133 -0
  224. package/dist/utils/cleanup-manager.d.ts.map +1 -0
  225. package/dist/utils/cleanup-manager.js +673 -0
  226. package/dist/utils/cleanup-manager.js.map +1 -0
  227. package/dist/utils/cli-handler.d.ts +16 -0
  228. package/dist/utils/cli-handler.d.ts.map +1 -0
  229. package/dist/utils/cli-handler.js +102 -0
  230. package/dist/utils/cli-handler.js.map +1 -0
  231. package/dist/utils/logger.d.ts +61 -0
  232. package/dist/utils/logger.d.ts.map +1 -0
  233. package/dist/utils/logger.js +92 -0
  234. package/dist/utils/logger.js.map +1 -0
  235. package/dist/utils/page-utils.d.ts +54 -0
  236. package/dist/utils/page-utils.d.ts.map +1 -0
  237. package/dist/utils/page-utils.js +381 -0
  238. package/dist/utils/page-utils.js.map +1 -0
  239. package/dist/utils/rate-limit-handler.d.ts +42 -0
  240. package/dist/utils/rate-limit-handler.d.ts.map +1 -0
  241. package/dist/utils/rate-limit-handler.js +88 -0
  242. package/dist/utils/rate-limit-handler.js.map +1 -0
  243. package/dist/utils/rate-limit-handler.test.d.ts +7 -0
  244. package/dist/utils/rate-limit-handler.test.d.ts.map +1 -0
  245. package/dist/utils/rate-limit-handler.test.js +91 -0
  246. package/dist/utils/rate-limit-handler.test.js.map +1 -0
  247. package/dist/utils/settings-manager.d.ts +37 -0
  248. package/dist/utils/settings-manager.d.ts.map +1 -0
  249. package/dist/utils/settings-manager.js +121 -0
  250. package/dist/utils/settings-manager.js.map +1 -0
  251. package/dist/utils/stealth-utils.d.ts +135 -0
  252. package/dist/utils/stealth-utils.d.ts.map +1 -0
  253. package/dist/utils/stealth-utils.js +396 -0
  254. package/dist/utils/stealth-utils.js.map +1 -0
  255. package/dist/utils/stealth-utils.test.d.ts +7 -0
  256. package/dist/utils/stealth-utils.test.d.ts.map +1 -0
  257. package/dist/utils/stealth-utils.test.js +72 -0
  258. package/dist/utils/stealth-utils.test.js.map +1 -0
  259. package/docs/01_configuration.md +94 -0
  260. package/docs/02_tools.md +34 -0
  261. package/docs/03_troubleshooting.md +59 -0
  262. package/docs/04_usage-guide.md +245 -0
  263. package/docs/05_project-analysis.qmd +309 -0
  264. package/docs/06_integration-analysis.html +914 -0
  265. package/docs/06_integration-analysis.qmd +255 -0
  266. package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap-4f0954b6b0dd6bf39f4bb9151ba984db.min.css +12 -0
  267. package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap-icons.css +2106 -0
  268. package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap-icons.woff +0 -0
  269. package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap.min.js +7 -0
  270. package/docs/06_integration-analysis_files/libs/clipboard/clipboard.min.js +7 -0
  271. package/docs/06_integration-analysis_files/libs/quarto-html/anchor.min.js +9 -0
  272. package/docs/06_integration-analysis_files/libs/quarto-html/axe/axe-check.js +145 -0
  273. package/docs/06_integration-analysis_files/libs/quarto-html/popper.min.js +6 -0
  274. package/docs/06_integration-analysis_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
  275. package/docs/06_integration-analysis_files/libs/quarto-html/quarto.js +847 -0
  276. package/docs/06_integration-analysis_files/libs/quarto-html/tabsets/tabsets.js +95 -0
  277. package/docs/06_integration-analysis_files/libs/quarto-html/tippy.css +1 -0
  278. package/docs/06_integration-analysis_files/libs/quarto-html/tippy.umd.min.js +2 -0
  279. package/docs/07_e2e-testing-safety.qmd +754 -0
  280. package/docs/08_project-re-evaluation.html +609 -0
  281. package/docs/08_project-re-evaluation.qmd +86 -0
  282. package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap-4f0954b6b0dd6bf39f4bb9151ba984db.min.css +12 -0
  283. package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap-icons.css +2106 -0
  284. package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap-icons.woff +0 -0
  285. package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap.min.js +7 -0
  286. package/docs/08_project-re-evaluation_files/libs/clipboard/clipboard.min.js +7 -0
  287. package/docs/08_project-re-evaluation_files/libs/quarto-html/anchor.min.js +9 -0
  288. package/docs/08_project-re-evaluation_files/libs/quarto-html/axe/axe-check.js +145 -0
  289. package/docs/08_project-re-evaluation_files/libs/quarto-html/popper.min.js +6 -0
  290. package/docs/08_project-re-evaluation_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
  291. package/docs/08_project-re-evaluation_files/libs/quarto-html/quarto.js +847 -0
  292. package/docs/08_project-re-evaluation_files/libs/quarto-html/tabsets/tabsets.js +95 -0
  293. package/docs/08_project-re-evaluation_files/libs/quarto-html/tippy.css +1 -0
  294. package/docs/08_project-re-evaluation_files/libs/quarto-html/tippy.umd.min.js +2 -0
  295. package/docs/notebooklm-mcp-usage.html +704 -0
  296. package/docs/notebooklm-mcp-usage.qmd +119 -0
  297. package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap-6b71f2156b6a5230c6677325978bcf08.min.css +12 -0
  298. package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap-icons.css +2106 -0
  299. package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap-icons.woff +0 -0
  300. package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap.min.js +7 -0
  301. package/docs/notebooklm-mcp-usage_files/libs/clipboard/clipboard.min.js +7 -0
  302. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/anchor.min.js +9 -0
  303. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/axe/axe-check.js +145 -0
  304. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/popper.min.js +6 -0
  305. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
  306. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/quarto.js +847 -0
  307. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/tabsets/tabsets.js +95 -0
  308. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/tippy.css +1 -0
  309. package/docs/notebooklm-mcp-usage_files/libs/quarto-html/tippy.umd.min.js +2 -0
  310. package/docs/repomix-usage_files/libs/bootstrap/bootstrap-6b71f2156b6a5230c6677325978bcf08.min.css +12 -0
  311. package/docs/repomix-usage_files/libs/bootstrap/bootstrap-icons.css +2106 -0
  312. package/docs/repomix-usage_files/libs/bootstrap/bootstrap-icons.woff +0 -0
  313. package/docs/repomix-usage_files/libs/bootstrap/bootstrap.min.js +7 -0
  314. package/docs/repomix-usage_files/libs/clipboard/clipboard.min.js +7 -0
  315. package/docs/repomix-usage_files/libs/quarto-html/anchor.min.js +9 -0
  316. package/docs/repomix-usage_files/libs/quarto-html/axe/axe-check.js +145 -0
  317. package/docs/repomix-usage_files/libs/quarto-html/popper.min.js +6 -0
  318. package/docs/repomix-usage_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
  319. package/docs/repomix-usage_files/libs/quarto-html/quarto.js +847 -0
  320. package/docs/repomix-usage_files/libs/quarto-html/tabsets/tabsets.js +95 -0
  321. package/docs/repomix-usage_files/libs/quarto-html/tippy.css +1 -0
  322. package/docs/repomix-usage_files/libs/quarto-html/tippy.umd.min.js +2 -0
  323. package/package.json +62 -0
@@ -0,0 +1,960 @@
1
+ /**
2
+ * Authentication Manager for NotebookLM
3
+ *
4
+ * Handles:
5
+ * - Interactive login (headful browser for setup)
6
+ * - Auto-login with credentials (email/password from ENV)
7
+ * - Browser state persistence (cookies + localStorage + sessionStorage)
8
+ * - Cookie expiry validation
9
+ * - State expiry checks (24h file age)
10
+ * - Hard reset for clean start
11
+ *
12
+ * Based on the Python implementation from auth.py
13
+ */
14
+ import fs from "fs/promises";
15
+ import { existsSync } from "fs";
16
+ import path from "path";
17
+ import { CONFIG, NOTEBOOKLM_AUTH_URL } from "../config.js";
18
+ import { log } from "../utils/logger.js";
19
+ import { humanType, randomDelay, realisticClick, randomMouseMovement, } from "../utils/stealth-utils.js";
20
+ import { CRITICAL_COOKIE_NAMES, NOTEBOOKLM_URL_PREFIX } from "../constants.js";
21
+ export class AuthManager {
22
+ stateFilePath;
23
+ sessionFilePath;
24
+ constructor() {
25
+ this.stateFilePath = path.join(CONFIG.browserStateDir, "state.json");
26
+ this.sessionFilePath = path.join(CONFIG.browserStateDir, "session.json");
27
+ }
28
+ // ============================================================================
29
+ // Browser State Management
30
+ // ============================================================================
31
+ /**
32
+ * Save entire browser state (cookies + localStorage)
33
+ */
34
+ async saveBrowserState(context, page) {
35
+ try {
36
+ // Save storage state (cookies + localStorage + IndexedDB)
37
+ await context.storageState({ path: this.stateFilePath });
38
+ // Also save sessionStorage if page is provided
39
+ if (page) {
40
+ try {
41
+ const sessionStorageData = await page.evaluate(() => {
42
+ // Properly extract sessionStorage as a plain object
43
+ const storage = {};
44
+ for (let i = 0; i < sessionStorage.length; i++) {
45
+ const key = sessionStorage.key(i);
46
+ if (key) {
47
+ storage[key] = sessionStorage.getItem(key) || '';
48
+ }
49
+ }
50
+ return JSON.stringify(storage);
51
+ });
52
+ await fs.writeFile(this.sessionFilePath, sessionStorageData, {
53
+ encoding: "utf-8",
54
+ });
55
+ const entries = Object.keys(JSON.parse(sessionStorageData)).length;
56
+ log.success(`โœ… Browser state saved (incl. sessionStorage: ${entries} entries)`);
57
+ }
58
+ catch (error) {
59
+ log.warning(`โš ๏ธ State saved, but sessionStorage failed: ${error}`);
60
+ }
61
+ }
62
+ else {
63
+ log.success("โœ… Browser state saved");
64
+ }
65
+ return true;
66
+ }
67
+ catch (error) {
68
+ log.error(`โŒ Failed to save browser state: ${error}`);
69
+ return false;
70
+ }
71
+ }
72
+ /**
73
+ * Check if saved browser state exists
74
+ */
75
+ async hasSavedState() {
76
+ try {
77
+ await fs.access(this.stateFilePath);
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Get path to saved browser state
86
+ */
87
+ getStatePath() {
88
+ // Synchronous check using imported existsSync
89
+ if (existsSync(this.stateFilePath)) {
90
+ return this.stateFilePath;
91
+ }
92
+ return null;
93
+ }
94
+ /**
95
+ * Get valid state path (checks expiry)
96
+ */
97
+ async getValidStatePath() {
98
+ const statePath = this.getStatePath();
99
+ if (!statePath) {
100
+ return null;
101
+ }
102
+ if (await this.isStateExpired()) {
103
+ log.warning("โš ๏ธ Saved state is expired (>24h old)");
104
+ log.info("๐Ÿ’ก Run setup_auth tool to re-authenticate");
105
+ return null;
106
+ }
107
+ return statePath;
108
+ }
109
+ /**
110
+ * Load sessionStorage from file
111
+ */
112
+ async loadSessionStorage() {
113
+ try {
114
+ const data = await fs.readFile(this.sessionFilePath, { encoding: "utf-8" });
115
+ const sessionData = JSON.parse(data);
116
+ log.success(`โœ… Loaded sessionStorage (${Object.keys(sessionData).length} entries)`);
117
+ return sessionData;
118
+ }
119
+ catch (error) {
120
+ log.warning(`โš ๏ธ Failed to load sessionStorage: ${error}`);
121
+ return null;
122
+ }
123
+ }
124
+ // ============================================================================
125
+ // Cookie Validation
126
+ // ============================================================================
127
+ /**
128
+ * Validate if saved state is still valid
129
+ */
130
+ async validateState(context) {
131
+ try {
132
+ const cookies = await context.cookies();
133
+ if (cookies.length === 0) {
134
+ log.warning("โš ๏ธ No cookies found in state");
135
+ return false;
136
+ }
137
+ // Check for Google auth cookies
138
+ const googleCookies = cookies.filter((c) => c.domain.includes("google.com"));
139
+ if (googleCookies.length === 0) {
140
+ log.warning("โš ๏ธ No Google cookies found");
141
+ return false;
142
+ }
143
+ // Check if important cookies are expired
144
+ const currentTime = Date.now() / 1000;
145
+ for (const cookie of googleCookies) {
146
+ const expires = cookie.expires ?? -1;
147
+ if (expires !== -1 && expires < currentTime) {
148
+ log.warning(`โš ๏ธ Cookie '${cookie.name}' has expired`);
149
+ return false;
150
+ }
151
+ }
152
+ log.success("โœ… State validation passed");
153
+ return true;
154
+ }
155
+ catch (error) {
156
+ log.warning(`โš ๏ธ State validation failed: ${error}`);
157
+ return false;
158
+ }
159
+ }
160
+ /**
161
+ * Validate if critical authentication cookies are still valid
162
+ */
163
+ async validateCookiesExpiry(context) {
164
+ try {
165
+ const cookies = await context.cookies();
166
+ if (cookies.length === 0) {
167
+ log.warning("โš ๏ธ No cookies found");
168
+ return false;
169
+ }
170
+ // Find critical cookies
171
+ const criticalCookies = cookies.filter((c) => CRITICAL_COOKIE_NAMES.includes(c.name));
172
+ if (criticalCookies.length === 0) {
173
+ log.warning("โš ๏ธ No critical auth cookies found");
174
+ return false;
175
+ }
176
+ // Check expiration for each critical cookie
177
+ const currentTime = Date.now() / 1000;
178
+ const expiredCookies = [];
179
+ for (const cookie of criticalCookies) {
180
+ const expires = cookie.expires ?? -1;
181
+ // -1 means session cookie (valid until browser closes)
182
+ if (expires === -1) {
183
+ continue;
184
+ }
185
+ // Check if cookie is expired
186
+ if (expires < currentTime) {
187
+ expiredCookies.push(cookie.name);
188
+ }
189
+ }
190
+ if (expiredCookies.length > 0) {
191
+ log.warning(`โš ๏ธ Expired cookies: ${expiredCookies.join(", ")}`);
192
+ return false;
193
+ }
194
+ log.success(`โœ… All ${criticalCookies.length} critical cookies are valid`);
195
+ return true;
196
+ }
197
+ catch (error) {
198
+ log.warning(`โš ๏ธ Cookie validation failed: ${error}`);
199
+ return false;
200
+ }
201
+ }
202
+ /**
203
+ * Check if the saved state file is too old (>24 hours)
204
+ */
205
+ async isStateExpired() {
206
+ try {
207
+ const stats = await fs.stat(this.stateFilePath);
208
+ const fileAgeSeconds = (Date.now() - stats.mtimeMs) / 1000;
209
+ const maxAgeSeconds = 24 * 60 * 60; // 24 hours
210
+ if (fileAgeSeconds > maxAgeSeconds) {
211
+ const hoursOld = fileAgeSeconds / 3600;
212
+ log.warning(`โš ๏ธ Saved state is ${hoursOld.toFixed(1)}h old (max: 24h)`);
213
+ return true;
214
+ }
215
+ return false;
216
+ }
217
+ catch {
218
+ return true; // File doesn't exist = expired
219
+ }
220
+ }
221
+ // ============================================================================
222
+ // Interactive Login
223
+ // ============================================================================
224
+ /**
225
+ * Perform interactive login
226
+ * User will see a browser window and login manually
227
+ *
228
+ * SIMPLE & RELIABLE: Just wait for URL to change to notebooklm.google.com
229
+ */
230
+ async performLogin(page, sendProgress) {
231
+ try {
232
+ log.info("๐ŸŒ Opening Google login page...");
233
+ log.warning("๐Ÿ“ Please login to your Google account");
234
+ log.warning("โณ Browser will close automatically once you reach NotebookLM");
235
+ log.info("");
236
+ // Progress: Navigating
237
+ await sendProgress?.("Navigating to Google login...", 3, 10);
238
+ // Navigate to Google login (redirects to NotebookLM after auth)
239
+ await page.goto(NOTEBOOKLM_AUTH_URL, { timeout: 60000 });
240
+ // Progress: Waiting for login
241
+ await sendProgress?.("Waiting for manual login (up to 10 minutes)...", 4, 10);
242
+ // Wait for user to complete login
243
+ log.warning("โณ Waiting for login (up to 10 minutes)...");
244
+ const checkIntervalMs = 1000; // Check every 1 second
245
+ const maxAttempts = 600; // 10 minutes total
246
+ let lastProgressUpdate = 0;
247
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
248
+ try {
249
+ const currentUrl = page.url();
250
+ const elapsedSeconds = Math.floor(attempt * (checkIntervalMs / 1000));
251
+ // Send progress every 10 seconds
252
+ if (elapsedSeconds - lastProgressUpdate >= 10) {
253
+ lastProgressUpdate = elapsedSeconds;
254
+ const progressStep = Math.min(8, 4 + Math.floor(elapsedSeconds / 60));
255
+ await sendProgress?.(`Waiting for login... (${elapsedSeconds}s elapsed)`, progressStep, 10);
256
+ }
257
+ // โœ… SIMPLE: Check if we're on NotebookLM (any path!)
258
+ if (currentUrl.startsWith(NOTEBOOKLM_URL_PREFIX)) {
259
+ await sendProgress?.("Login successful! NotebookLM detected!", 9, 10);
260
+ log.success("โœ… Login successful! NotebookLM URL detected.");
261
+ log.success(`โœ… Current URL: ${currentUrl}`);
262
+ // Short wait to ensure page is loaded
263
+ await page.waitForTimeout(CONFIG.authPageStabilizeMs);
264
+ return true;
265
+ }
266
+ // Still on accounts.google.com - log periodically
267
+ if (currentUrl.includes("accounts.google.com") && attempt % 30 === 0 && attempt > 0) {
268
+ log.warning(`โณ Still waiting... (${elapsedSeconds}s elapsed)`);
269
+ }
270
+ await page.waitForTimeout(checkIntervalMs);
271
+ }
272
+ catch {
273
+ await page.waitForTimeout(checkIntervalMs);
274
+ continue;
275
+ }
276
+ }
277
+ // Timeout reached - final check
278
+ const currentUrl = page.url();
279
+ if (currentUrl.startsWith(NOTEBOOKLM_URL_PREFIX)) {
280
+ await sendProgress?.("Login successful (detected on timeout check)!", 9, 10);
281
+ log.success("โœ… Login successful (detected on timeout check)");
282
+ return true;
283
+ }
284
+ log.error("โŒ Login verification failed - timeout reached");
285
+ log.warning(`Current URL: ${currentUrl}`);
286
+ return false;
287
+ }
288
+ catch (error) {
289
+ log.error(`โŒ Login failed: ${error}`);
290
+ return false;
291
+ }
292
+ }
293
+ // ============================================================================
294
+ // Auto-Login with Credentials
295
+ // ============================================================================
296
+ /**
297
+ * Attempt to authenticate using configured credentials
298
+ */
299
+ async loginWithCredentials(context, page, email, password) {
300
+ const maskedEmail = this.maskEmail(email);
301
+ log.warning(`๐Ÿ” Attempting automatic login for ${maskedEmail}...`);
302
+ // Log browser visibility
303
+ if (!CONFIG.headless) {
304
+ log.info(" ๐Ÿ‘๏ธ Browser is VISIBLE for debugging");
305
+ }
306
+ else {
307
+ log.info(" ๐Ÿ™ˆ Browser is HEADLESS (invisible)");
308
+ }
309
+ log.info(` ๐ŸŒ Navigating to Google login...`);
310
+ try {
311
+ await page.goto(NOTEBOOKLM_AUTH_URL, {
312
+ waitUntil: "domcontentloaded",
313
+ timeout: CONFIG.browserTimeout,
314
+ });
315
+ log.success(` โœ… Page loaded: ${page.url().slice(0, 80)}...`);
316
+ }
317
+ catch (error) {
318
+ log.warning(` โš ๏ธ Page load timeout (continuing anyway)`);
319
+ }
320
+ const deadline = Date.now() + CONFIG.autoLoginTimeoutMs;
321
+ log.info(` โฐ Auto-login timeout: ${CONFIG.autoLoginTimeoutMs / 1000}s`);
322
+ // Already on NotebookLM?
323
+ log.info(" ๐Ÿ” Checking if already authenticated...");
324
+ if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
325
+ log.success("โœ… Already authenticated");
326
+ await this.saveBrowserState(context, page);
327
+ return true;
328
+ }
329
+ log.warning(" โŒ Not authenticated yet, proceeding with login...");
330
+ // Handle possible account chooser
331
+ log.info(" ๐Ÿ” Checking for account chooser...");
332
+ if (await this.handleAccountChooser(page, email)) {
333
+ log.success(" โœ… Account selected from chooser");
334
+ if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
335
+ log.success("โœ… Automatic login successful");
336
+ await this.saveBrowserState(context, page);
337
+ return true;
338
+ }
339
+ }
340
+ // Email step
341
+ log.info(" ๐Ÿ“ง Entering email address...");
342
+ if (!(await this.fillIdentifier(page, email))) {
343
+ if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
344
+ log.success("โœ… Automatic login successful");
345
+ await this.saveBrowserState(context, page);
346
+ return true;
347
+ }
348
+ log.warning("โš ๏ธ Email input not detected");
349
+ }
350
+ // Password step (wait until visible)
351
+ let waitAttempts = 0;
352
+ log.warning(" โณ Waiting for password page to load...");
353
+ while (Date.now() < deadline && !(await this.fillPassword(page, password))) {
354
+ waitAttempts++;
355
+ // Log every 10 seconds (20 attempts * 0.5s)
356
+ if (waitAttempts % 20 === 0) {
357
+ const secondsWaited = waitAttempts * 0.5;
358
+ const secondsRemaining = (deadline - Date.now()) / 1000;
359
+ log.warning(` โณ Still waiting for password field... (${secondsWaited}s elapsed, ${secondsRemaining.toFixed(0)}s remaining)`);
360
+ log.info(` ๐Ÿ“ Current URL: ${page.url().slice(0, 100)}`);
361
+ }
362
+ if (page.url().includes("challenge")) {
363
+ log.warning("โš ๏ธ Additional verification required (Google challenge page).");
364
+ return false;
365
+ }
366
+ await page.waitForTimeout(CONFIG.authActionDelayMs);
367
+ }
368
+ // Wait for Google redirect after login
369
+ log.info(" ๐Ÿ”„ Waiting for Google redirect to NotebookLM...");
370
+ if (await this.waitForRedirectAfterLogin(page, deadline)) {
371
+ log.success("โœ… Automatic login successful");
372
+ await this.saveBrowserState(context, page);
373
+ return true;
374
+ }
375
+ // Login failed - diagnose
376
+ log.error("โŒ Automatic login timed out");
377
+ // Take screenshot for debugging
378
+ try {
379
+ const screenshotPath = path.join(CONFIG.dataDir, `login_failed_${Date.now()}.png`);
380
+ await page.screenshot({ path: screenshotPath });
381
+ log.info(` ๐Ÿ“ธ Screenshot saved: ${screenshotPath}`);
382
+ }
383
+ catch (error) {
384
+ log.warning(` โš ๏ธ Could not save screenshot: ${error}`);
385
+ }
386
+ // Diagnose specific failure reason
387
+ const currentUrl = page.url();
388
+ log.warning(" ๐Ÿ” Diagnosing failure...");
389
+ if (currentUrl.includes("accounts.google.com")) {
390
+ if (currentUrl.includes("/signin/identifier")) {
391
+ log.error(" โŒ Still on email page - email input might have failed");
392
+ log.info(" ๐Ÿ’ก Check if email is correct in .env");
393
+ }
394
+ else if (currentUrl.includes("/challenge")) {
395
+ log.error(" โŒ Google requires additional verification (2FA, CAPTCHA, suspicious login)");
396
+ log.info(" ๐Ÿ’ก Try logging in manually first: use setup_auth tool");
397
+ }
398
+ else if (currentUrl.includes("/pwd") || currentUrl.includes("/password")) {
399
+ log.error(" โŒ Still on password page - password input might have failed");
400
+ log.info(" ๐Ÿ’ก Check if password is correct in .env");
401
+ }
402
+ else {
403
+ log.error(` โŒ Stuck on Google accounts page: ${currentUrl.slice(0, 80)}...`);
404
+ }
405
+ }
406
+ else if (currentUrl.includes("notebooklm.google.com")) {
407
+ log.warning(" โš ๏ธ Reached NotebookLM but couldn't detect successful login");
408
+ log.info(" ๐Ÿ’ก This might be a timing issue - try again");
409
+ }
410
+ else {
411
+ log.error(` โŒ Unexpected page: ${currentUrl.slice(0, 80)}...`);
412
+ }
413
+ return false;
414
+ }
415
+ // ============================================================================
416
+ // Helper Methods
417
+ // ============================================================================
418
+ /**
419
+ * Wait for Google to redirect to NotebookLM after successful login (SIMPLE & RELIABLE)
420
+ *
421
+ * Just checks if URL changes to notebooklm.google.com - no complex UI element searching!
422
+ * Matches the simplified approach used in performLogin().
423
+ */
424
+ async waitForRedirectAfterLogin(page, deadline) {
425
+ log.info(" โณ Waiting for redirect to NotebookLM...");
426
+ while (Date.now() < deadline) {
427
+ try {
428
+ const currentUrl = page.url();
429
+ // Simple check: Are we on NotebookLM?
430
+ if (currentUrl.startsWith(NOTEBOOKLM_URL_PREFIX)) {
431
+ log.success(" โœ… NotebookLM URL detected!");
432
+ // Short wait to ensure page is loaded
433
+ await page.waitForTimeout(CONFIG.authPageStabilizeMs);
434
+ return true;
435
+ }
436
+ }
437
+ catch {
438
+ // Ignore errors
439
+ }
440
+ await page.waitForTimeout(CONFIG.authActionDelayMs);
441
+ }
442
+ log.error(" โŒ Redirect timeout - NotebookLM URL not reached");
443
+ return false;
444
+ }
445
+ /**
446
+ * Wait for NotebookLM to load (SIMPLE & RELIABLE)
447
+ *
448
+ * Just checks if URL starts with notebooklm.google.com - no complex UI element searching!
449
+ * Matches the simplified approach used in performLogin().
450
+ */
451
+ async waitForNotebook(page, timeoutMs) {
452
+ const endTime = Date.now() + timeoutMs;
453
+ while (Date.now() < endTime) {
454
+ try {
455
+ const currentUrl = page.url();
456
+ // Simple check: Are we on NotebookLM?
457
+ if (currentUrl.startsWith(NOTEBOOKLM_URL_PREFIX)) {
458
+ log.success(" โœ… NotebookLM URL detected");
459
+ return true;
460
+ }
461
+ }
462
+ catch {
463
+ // Ignore errors
464
+ }
465
+ await page.waitForTimeout(CONFIG.authCheckIntervalMs);
466
+ }
467
+ return false;
468
+ }
469
+ /**
470
+ * Handle possible account chooser
471
+ */
472
+ async handleAccountChooser(page, email) {
473
+ try {
474
+ const chooser = await page.$$("div[data-identifier], li[data-identifier]");
475
+ if (chooser.length > 0) {
476
+ for (const item of chooser) {
477
+ const identifier = (await item.getAttribute("data-identifier"))?.toLowerCase() || "";
478
+ if (identifier === email.toLowerCase()) {
479
+ await item.click();
480
+ await randomDelay(150, 320);
481
+ await page.waitForTimeout(CONFIG.authActionDelayMs);
482
+ return true;
483
+ }
484
+ }
485
+ // Click "Use another account"
486
+ await this.clickText(page, [
487
+ "Use another account",
488
+ "Weiteres Konto hinzufรผgen",
489
+ "Anderes Konto verwenden",
490
+ ]);
491
+ await randomDelay(150, 320);
492
+ return false;
493
+ }
494
+ return false;
495
+ }
496
+ catch {
497
+ return false;
498
+ }
499
+ }
500
+ /**
501
+ * Fill email identifier field with human-like typing
502
+ */
503
+ async fillIdentifier(page, email) {
504
+ log.info(" ๐Ÿ“ง Looking for email field...");
505
+ const emailSelectors = [
506
+ "input#identifierId",
507
+ "input[name='identifier']",
508
+ "input[type='email']",
509
+ ];
510
+ let emailSelector = null;
511
+ let emailField = null;
512
+ for (const selector of emailSelectors) {
513
+ try {
514
+ const candidate = await page.waitForSelector(selector, {
515
+ state: "attached",
516
+ timeout: 3000,
517
+ });
518
+ if (!candidate)
519
+ continue;
520
+ try {
521
+ if (!(await candidate.isVisible())) {
522
+ continue; // Hidden field
523
+ }
524
+ }
525
+ catch {
526
+ continue;
527
+ }
528
+ emailField = candidate;
529
+ emailSelector = selector;
530
+ log.success(` โœ… Email field visible: ${selector}`);
531
+ break;
532
+ }
533
+ catch {
534
+ continue;
535
+ }
536
+ }
537
+ if (!emailField || !emailSelector) {
538
+ log.warning(" โ„น๏ธ No visible email field found (likely pre-filled)");
539
+ log.info(` ๐Ÿ“ Current URL: ${page.url().slice(0, 100)}`);
540
+ return false;
541
+ }
542
+ // Human-like mouse movement to field
543
+ try {
544
+ const box = await emailField.boundingBox();
545
+ if (box) {
546
+ const targetX = box.x + box.width / 2;
547
+ const targetY = box.y + box.height / 2;
548
+ await randomMouseMovement(page, targetX, targetY);
549
+ await randomDelay(200, 500);
550
+ }
551
+ }
552
+ catch {
553
+ // Ignore errors
554
+ }
555
+ // Click to focus
556
+ try {
557
+ await realisticClick(page, emailSelector, false);
558
+ }
559
+ catch (error) {
560
+ log.warning(` โš ๏ธ Could not click email field (${error}); trying direct focus`);
561
+ try {
562
+ await emailField.focus();
563
+ }
564
+ catch {
565
+ log.error(" โŒ Failed to focus email field");
566
+ return false;
567
+ }
568
+ }
569
+ // โœ… FASTER: Programmer typing speed (90-120 WPM from config)
570
+ log.info(` โŒจ๏ธ Typing email: ${this.maskEmail(email)}`);
571
+ try {
572
+ const wpm = CONFIG.typingWpmMin + Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
573
+ await humanType(page, emailSelector, email, { wpm, withTypos: false });
574
+ log.success(" โœ… Email typed successfully");
575
+ }
576
+ catch (error) {
577
+ log.error(` โŒ Typing failed: ${error}`);
578
+ try {
579
+ await page.fill(emailSelector, email);
580
+ log.success(" โœ… Filled email using fallback");
581
+ }
582
+ catch {
583
+ return false;
584
+ }
585
+ }
586
+ // Human "thinking" pause before clicking Next
587
+ await randomDelay(400, 1200);
588
+ // Click Next button
589
+ log.info(" ๐Ÿ”˜ Looking for Next button...");
590
+ const nextSelectors = [
591
+ "button:has-text('Next')",
592
+ "button:has-text('Weiter')",
593
+ "#identifierNext",
594
+ ];
595
+ let nextClicked = false;
596
+ for (const selector of nextSelectors) {
597
+ try {
598
+ const button = await page.locator(selector);
599
+ if ((await button.count()) > 0) {
600
+ await realisticClick(page, selector, true);
601
+ log.success(` โœ… Next button clicked: ${selector}`);
602
+ nextClicked = true;
603
+ break;
604
+ }
605
+ }
606
+ catch {
607
+ continue;
608
+ }
609
+ }
610
+ if (!nextClicked) {
611
+ log.warning(" โš ๏ธ Button not found, pressing Enter");
612
+ await emailField.press("Enter");
613
+ }
614
+ // Variable delay
615
+ await randomDelay(800, 1500);
616
+ log.success(" โœ… Email step complete");
617
+ return true;
618
+ }
619
+ /**
620
+ * Fill password field with human-like typing
621
+ */
622
+ async fillPassword(page, password) {
623
+ log.info(" ๐Ÿ” Looking for password field...");
624
+ const passwordSelectors = ["input[name='Passwd']", "input[type='password']"];
625
+ let passwordSelector = null;
626
+ let passwordField = null;
627
+ for (const selector of passwordSelectors) {
628
+ try {
629
+ passwordField = await page.$(selector);
630
+ if (passwordField) {
631
+ passwordSelector = selector;
632
+ log.success(` โœ… Password field found: ${selector}`);
633
+ break;
634
+ }
635
+ }
636
+ catch {
637
+ continue;
638
+ }
639
+ }
640
+ if (!passwordField) {
641
+ // Not found yet, but don't fail - this is called in a loop
642
+ return false;
643
+ }
644
+ // Human-like mouse movement to field
645
+ try {
646
+ const box = await passwordField.boundingBox();
647
+ if (box) {
648
+ const targetX = box.x + box.width / 2;
649
+ const targetY = box.y + box.height / 2;
650
+ await randomMouseMovement(page, targetX, targetY);
651
+ await randomDelay(300, 700);
652
+ }
653
+ }
654
+ catch {
655
+ // Ignore errors
656
+ }
657
+ // Click to focus
658
+ if (passwordSelector) {
659
+ await realisticClick(page, passwordSelector, false);
660
+ }
661
+ // โœ… FASTER: Programmer typing speed (90-120 WPM from config)
662
+ log.info(" โŒจ๏ธ Typing password...");
663
+ try {
664
+ const wpm = CONFIG.typingWpmMin + Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
665
+ if (passwordSelector) {
666
+ await humanType(page, passwordSelector, password, { wpm, withTypos: false });
667
+ }
668
+ log.success(" โœ… Password typed successfully");
669
+ }
670
+ catch (error) {
671
+ log.error(` โŒ Typing failed: ${error}`);
672
+ return false;
673
+ }
674
+ // Human "review" pause before submitting password
675
+ await randomDelay(300, 1000);
676
+ // Click Next button
677
+ log.info(" ๐Ÿ”˜ Looking for Next button...");
678
+ const pwdNextSelectors = [
679
+ "button:has-text('Next')",
680
+ "button:has-text('Weiter')",
681
+ "#passwordNext",
682
+ ];
683
+ let pwdNextClicked = false;
684
+ for (const selector of pwdNextSelectors) {
685
+ try {
686
+ const button = await page.locator(selector);
687
+ if ((await button.count()) > 0) {
688
+ await realisticClick(page, selector, true);
689
+ log.success(` โœ… Next button clicked: ${selector}`);
690
+ pwdNextClicked = true;
691
+ break;
692
+ }
693
+ }
694
+ catch {
695
+ continue;
696
+ }
697
+ }
698
+ if (!pwdNextClicked) {
699
+ log.warning(" โš ๏ธ Button not found, pressing Enter");
700
+ await passwordField.press("Enter");
701
+ }
702
+ // Variable delay
703
+ await randomDelay(800, 1500);
704
+ log.success(" โœ… Password step complete");
705
+ return true;
706
+ }
707
+ /**
708
+ * Click text element
709
+ */
710
+ async clickText(page, texts) {
711
+ for (const text of texts) {
712
+ const selector = `text="${text}"`;
713
+ try {
714
+ const locator = page.locator(selector);
715
+ if ((await locator.count()) > 0) {
716
+ await realisticClick(page, selector, true);
717
+ await randomDelay(120, 260);
718
+ return true;
719
+ }
720
+ }
721
+ catch {
722
+ continue;
723
+ }
724
+ }
725
+ return false;
726
+ }
727
+ /**
728
+ * Mask email for logging
729
+ */
730
+ maskEmail(email) {
731
+ if (!email.includes("@")) {
732
+ return "***";
733
+ }
734
+ const [name, domain] = email.split("@");
735
+ if (name.length <= 2) {
736
+ return `${"*".repeat(name.length)}@${domain}`;
737
+ }
738
+ return `${name[0]}${"*".repeat(name.length - 2)}${name[name.length - 1]}@${domain}`;
739
+ }
740
+ // ============================================================================
741
+ // Additional Helper Methods
742
+ // ============================================================================
743
+ /**
744
+ * Load authentication state from a specific file path
745
+ */
746
+ async loadAuthState(context, statePath) {
747
+ try {
748
+ // Read state.json
749
+ const stateData = await fs.readFile(statePath, { encoding: "utf-8" });
750
+ const state = JSON.parse(stateData);
751
+ // Add cookies to context
752
+ if (state.cookies) {
753
+ await context.addCookies(state.cookies);
754
+ log.success(`โœ… Loaded ${state.cookies.length} cookies from ${statePath}`);
755
+ return true;
756
+ }
757
+ log.warning(`โš ๏ธ No cookies found in state file`);
758
+ return false;
759
+ }
760
+ catch (error) {
761
+ log.error(`โŒ Failed to load auth state: ${error}`);
762
+ return false;
763
+ }
764
+ }
765
+ /**
766
+ * Perform interactive setup (for setup_auth tool)
767
+ * Opens a PERSISTENT browser for manual login
768
+ *
769
+ * CRITICAL: Uses the SAME persistent context as runtime!
770
+ * This ensures cookies are automatically saved to the Chrome profile.
771
+ *
772
+ * Benefits over temporary browser:
773
+ * - Session cookies persist correctly (Playwright bug workaround)
774
+ * - Same fingerprint as runtime
775
+ * - No need for addCookies() workarounds
776
+ * - Automatic cookie persistence via Chrome profile
777
+ *
778
+ * @param sendProgress Optional progress callback
779
+ * @param overrideHeadless Optional override for headless mode (true = visible, false = headless)
780
+ * If not provided, defaults to true (visible) for setup
781
+ */
782
+ async performSetup(sendProgress, overrideHeadless) {
783
+ const { chromium } = await import("patchright");
784
+ // Determine headless mode: override or default to true (visible for setup)
785
+ // overrideHeadless contains show_browser value (true = show, false = hide)
786
+ const shouldShowBrowser = overrideHeadless !== undefined ? overrideHeadless : true;
787
+ try {
788
+ // CRITICAL: Clear ALL old auth data FIRST (for account switching)
789
+ log.info("๐Ÿ”„ Preparing for new account authentication...");
790
+ await sendProgress?.("Clearing old authentication data...", 1, 10);
791
+ await this.clearAllAuthData();
792
+ log.info("๐Ÿš€ Launching persistent browser for interactive setup...");
793
+ log.info(` ๐Ÿ“ Profile: ${CONFIG.chromeProfileDir}`);
794
+ await sendProgress?.("Launching persistent browser...", 2, 10);
795
+ // โœ… CRITICAL FIX: Use launchPersistentContext (same as runtime!)
796
+ // This ensures session cookies persist correctly
797
+ const context = await chromium.launchPersistentContext(CONFIG.chromeProfileDir, {
798
+ headless: !shouldShowBrowser, // Use override or default to visible for setup
799
+ channel: "chrome",
800
+ viewport: CONFIG.viewport,
801
+ locale: "en-US",
802
+ timezoneId: "Europe/Berlin",
803
+ args: [
804
+ "--disable-blink-features=AutomationControlled",
805
+ "--disable-dev-shm-usage",
806
+ "--no-first-run",
807
+ "--no-default-browser-check",
808
+ ],
809
+ });
810
+ // Get or create a page
811
+ const pages = context.pages();
812
+ const page = pages.length > 0 ? pages[0] : await context.newPage();
813
+ // Perform login with progress updates
814
+ const loginSuccess = await this.performLogin(page, sendProgress);
815
+ if (loginSuccess) {
816
+ // โœ… Save browser state to state.json (for validation & backup)
817
+ // Chrome ALSO saves everything to the persistent profile automatically!
818
+ await sendProgress?.("Saving authentication state...", 9, 10);
819
+ await this.saveBrowserState(context, page);
820
+ log.success("โœ… Setup complete - authentication saved to:");
821
+ log.success(` ๐Ÿ“„ State file: ${this.stateFilePath}`);
822
+ log.success(` ๐Ÿ“ Chrome profile: ${CONFIG.chromeProfileDir}`);
823
+ log.info("๐Ÿ’ก Session cookies will now persist across restarts!");
824
+ }
825
+ // Close persistent context
826
+ await context.close();
827
+ return loginSuccess;
828
+ }
829
+ catch (error) {
830
+ log.error(`โŒ Setup failed: ${error}`);
831
+ return false;
832
+ }
833
+ }
834
+ // ============================================================================
835
+ // Cleanup
836
+ // ============================================================================
837
+ /**
838
+ * Clear ALL authentication data for account switching
839
+ *
840
+ * CRITICAL: This deletes EVERYTHING to ensure only ONE account is active:
841
+ * - All state.json files (cookies, localStorage)
842
+ * - sessionStorage files
843
+ * - Chrome profile directory (browser fingerprint, cache, etc.)
844
+ *
845
+ * Use this BEFORE authenticating a new account!
846
+ */
847
+ async clearAllAuthData() {
848
+ log.warning("๐Ÿ—‘๏ธ Clearing ALL authentication data for account switch...");
849
+ let deletedCount = 0;
850
+ // 1. Delete all state files in browser_state_dir
851
+ try {
852
+ const files = await fs.readdir(CONFIG.browserStateDir);
853
+ for (const file of files) {
854
+ if (file.endsWith(".json")) {
855
+ await fs.unlink(path.join(CONFIG.browserStateDir, file));
856
+ log.info(` โœ… Deleted: ${file}`);
857
+ deletedCount++;
858
+ }
859
+ }
860
+ }
861
+ catch (error) {
862
+ log.warning(` โš ๏ธ Could not delete state files: ${error}`);
863
+ }
864
+ // 2. Delete Chrome profile (THE KEY for account switching!)
865
+ // This removes ALL browser data: cookies, cache, fingerprint, etc.
866
+ try {
867
+ const chromeProfileDir = CONFIG.chromeProfileDir;
868
+ if (existsSync(chromeProfileDir)) {
869
+ await fs.rm(chromeProfileDir, { recursive: true, force: true });
870
+ log.success(` โœ… Deleted Chrome profile: ${chromeProfileDir}`);
871
+ deletedCount++;
872
+ }
873
+ }
874
+ catch (error) {
875
+ log.warning(` โš ๏ธ Could not delete Chrome profile: ${error}`);
876
+ }
877
+ if (deletedCount === 0) {
878
+ log.info(" โ„น๏ธ No old auth data found (already clean)");
879
+ }
880
+ else {
881
+ log.success(`โœ… All auth data cleared (${deletedCount} items) - ready for new account!`);
882
+ }
883
+ }
884
+ /**
885
+ * Clear all saved authentication state
886
+ */
887
+ async clearState() {
888
+ try {
889
+ try {
890
+ await fs.unlink(this.stateFilePath);
891
+ }
892
+ catch {
893
+ // File doesn't exist
894
+ }
895
+ try {
896
+ await fs.unlink(this.sessionFilePath);
897
+ }
898
+ catch {
899
+ // File doesn't exist
900
+ }
901
+ log.success("โœ… Authentication state cleared");
902
+ return true;
903
+ }
904
+ catch (error) {
905
+ log.error(`โŒ Failed to clear state: ${error}`);
906
+ return false;
907
+ }
908
+ }
909
+ /**
910
+ * HARD RESET: Completely delete ALL authentication state
911
+ */
912
+ async hardResetState() {
913
+ try {
914
+ log.warning("๐Ÿงน Performing HARD RESET of all authentication state...");
915
+ let deletedCount = 0;
916
+ // Delete state file
917
+ try {
918
+ await fs.unlink(this.stateFilePath);
919
+ log.info(` ๐Ÿ—‘๏ธ Deleted: ${this.stateFilePath}`);
920
+ deletedCount++;
921
+ }
922
+ catch {
923
+ // File doesn't exist
924
+ }
925
+ // Delete session file
926
+ try {
927
+ await fs.unlink(this.sessionFilePath);
928
+ log.info(` ๐Ÿ—‘๏ธ Deleted: ${this.sessionFilePath}`);
929
+ deletedCount++;
930
+ }
931
+ catch {
932
+ // File doesn't exist
933
+ }
934
+ // Delete entire browser_state_dir
935
+ try {
936
+ const files = await fs.readdir(CONFIG.browserStateDir);
937
+ for (const file of files) {
938
+ await fs.unlink(path.join(CONFIG.browserStateDir, file));
939
+ deletedCount++;
940
+ }
941
+ log.info(` ๐Ÿ—‘๏ธ Deleted: ${CONFIG.browserStateDir}/ (${files.length} files)`);
942
+ }
943
+ catch {
944
+ // Directory doesn't exist or empty
945
+ }
946
+ if (deletedCount === 0) {
947
+ log.info(" โ„น๏ธ No state to delete (already clean)");
948
+ }
949
+ else {
950
+ log.success(`โœ… Hard reset complete: ${deletedCount} items deleted`);
951
+ }
952
+ return true;
953
+ }
954
+ catch (error) {
955
+ log.error(`โŒ Hard reset failed: ${error}`);
956
+ return false;
957
+ }
958
+ }
959
+ }
960
+ //# sourceMappingURL=auth-manager.js.map