@roomi-fields/notebooklm-mcp 1.3.3 โ†’ 1.3.5

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 (108) hide show
  1. package/README.md +764 -715
  2. package/dist/__tests__/cleanup-manager.test.d.ts +2 -0
  3. package/dist/__tests__/cleanup-manager.test.d.ts.map +1 -0
  4. package/dist/__tests__/cleanup-manager.test.js +341 -0
  5. package/dist/__tests__/cleanup-manager.test.js.map +1 -0
  6. package/dist/__tests__/config-parsing.test.d.ts +2 -0
  7. package/dist/__tests__/config-parsing.test.d.ts.map +1 -0
  8. package/dist/__tests__/config-parsing.test.js +338 -0
  9. package/dist/__tests__/config-parsing.test.js.map +1 -0
  10. package/dist/__tests__/config.test.d.ts +2 -0
  11. package/dist/__tests__/config.test.d.ts.map +1 -0
  12. package/dist/__tests__/config.test.js +267 -0
  13. package/dist/__tests__/config.test.js.map +1 -0
  14. package/dist/__tests__/errors.test.d.ts +2 -0
  15. package/dist/__tests__/errors.test.d.ts.map +1 -0
  16. package/dist/__tests__/errors.test.js +166 -0
  17. package/dist/__tests__/errors.test.js.map +1 -0
  18. package/dist/__tests__/logger.test.d.ts +2 -0
  19. package/dist/__tests__/logger.test.d.ts.map +1 -0
  20. package/dist/__tests__/logger.test.js +324 -0
  21. package/dist/__tests__/logger.test.js.map +1 -0
  22. package/dist/__tests__/page-utils.test.d.ts +2 -0
  23. package/dist/__tests__/page-utils.test.d.ts.map +1 -0
  24. package/dist/__tests__/page-utils.test.js +349 -0
  25. package/dist/__tests__/page-utils.test.js.map +1 -0
  26. package/dist/__tests__/setup-verification.test.d.ts +2 -0
  27. package/dist/__tests__/setup-verification.test.d.ts.map +1 -0
  28. package/dist/__tests__/setup-verification.test.js +15 -0
  29. package/dist/__tests__/setup-verification.test.js.map +1 -0
  30. package/dist/__tests__/stealth-utils.test.d.ts +2 -0
  31. package/dist/__tests__/stealth-utils.test.d.ts.map +1 -0
  32. package/dist/__tests__/stealth-utils.test.js +413 -0
  33. package/dist/__tests__/stealth-utils.test.js.map +1 -0
  34. package/dist/__tests__/types.test.d.ts +2 -0
  35. package/dist/__tests__/types.test.d.ts.map +1 -0
  36. package/dist/__tests__/types.test.js +461 -0
  37. package/dist/__tests__/types.test.js.map +1 -0
  38. package/dist/auth/auth-manager.d.ts +2 -2
  39. package/dist/auth/auth-manager.d.ts.map +1 -1
  40. package/dist/auth/auth-manager.js +187 -180
  41. package/dist/auth/auth-manager.js.map +1 -1
  42. package/dist/auto-discovery/auto-discovery.d.ts.map +1 -1
  43. package/dist/auto-discovery/auto-discovery.js +17 -17
  44. package/dist/auto-discovery/auto-discovery.js.map +1 -1
  45. package/dist/cli/de-auth.d.ts +10 -0
  46. package/dist/cli/de-auth.d.ts.map +1 -0
  47. package/dist/cli/de-auth.js +47 -0
  48. package/dist/cli/de-auth.js.map +1 -0
  49. package/dist/cli/help.d.ts +6 -0
  50. package/dist/cli/help.d.ts.map +1 -0
  51. package/dist/cli/help.js +67 -0
  52. package/dist/cli/help.js.map +1 -0
  53. package/dist/cli/setup-auth.d.ts +11 -0
  54. package/dist/cli/setup-auth.d.ts.map +1 -0
  55. package/dist/cli/setup-auth.js +82 -0
  56. package/dist/cli/setup-auth.js.map +1 -0
  57. package/dist/config.d.ts +1 -1
  58. package/dist/config.d.ts.map +1 -1
  59. package/dist/config.js +49 -103
  60. package/dist/config.js.map +1 -1
  61. package/dist/errors.d.ts.map +1 -1
  62. package/dist/errors.js +3 -3
  63. package/dist/errors.js.map +1 -1
  64. package/dist/http-wrapper.js +96 -172
  65. package/dist/http-wrapper.js.map +1 -1
  66. package/dist/index.js +110 -107
  67. package/dist/index.js.map +1 -1
  68. package/dist/library/notebook-library.d.ts +2 -2
  69. package/dist/library/notebook-library.d.ts.map +1 -1
  70. package/dist/library/notebook-library.js +24 -24
  71. package/dist/library/notebook-library.js.map +1 -1
  72. package/dist/session/browser-session.d.ts +4 -4
  73. package/dist/session/browser-session.d.ts.map +1 -1
  74. package/dist/session/browser-session.js +62 -58
  75. package/dist/session/browser-session.js.map +1 -1
  76. package/dist/session/session-manager.d.ts +3 -3
  77. package/dist/session/session-manager.d.ts.map +1 -1
  78. package/dist/session/session-manager.js +14 -14
  79. package/dist/session/session-manager.js.map +1 -1
  80. package/dist/session/shared-context-manager.d.ts +2 -2
  81. package/dist/session/shared-context-manager.d.ts.map +1 -1
  82. package/dist/session/shared-context-manager.js +62 -58
  83. package/dist/session/shared-context-manager.js.map +1 -1
  84. package/dist/tools/index.d.ts +14 -14
  85. package/dist/tools/index.d.ts.map +1 -1
  86. package/dist/tools/index.js +517 -530
  87. package/dist/tools/index.js.map +1 -1
  88. package/dist/types.d.ts +24 -67
  89. package/dist/types.d.ts.map +1 -1
  90. package/dist/types.js +1 -12
  91. package/dist/types.js.map +1 -1
  92. package/dist/utils/cleanup-manager.d.ts +1 -1
  93. package/dist/utils/cleanup-manager.d.ts.map +1 -1
  94. package/dist/utils/cleanup-manager.js +90 -92
  95. package/dist/utils/cleanup-manager.js.map +1 -1
  96. package/dist/utils/logger.d.ts +1 -2
  97. package/dist/utils/logger.d.ts.map +1 -1
  98. package/dist/utils/logger.js +15 -15
  99. package/dist/utils/page-utils.d.ts +1 -1
  100. package/dist/utils/page-utils.d.ts.map +1 -1
  101. package/dist/utils/page-utils.js +39 -46
  102. package/dist/utils/page-utils.js.map +1 -1
  103. package/dist/utils/stealth-utils.d.ts +2 -2
  104. package/dist/utils/stealth-utils.d.ts.map +1 -1
  105. package/dist/utils/stealth-utils.js +13 -13
  106. package/dist/utils/stealth-utils.js.map +1 -1
  107. package/docs/CHROME_PROFILE_LIMITATION.md +229 -212
  108. package/package.json +107 -78
@@ -11,32 +11,32 @@
11
11
  *
12
12
  * Based on the Python implementation from auth.py
13
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";
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
20
  /**
21
21
  * Critical cookie names for Google authentication
22
22
  */
23
23
  const CRITICAL_COOKIE_NAMES = [
24
- "SID",
25
- "HSID",
26
- "SSID", // Google session
27
- "APISID",
28
- "SAPISID", // API auth
29
- "OSID",
30
- "__Secure-OSID", // NotebookLM-specific
31
- "__Secure-1PSID",
32
- "__Secure-3PSID", // Secure variants
24
+ 'SID',
25
+ 'HSID',
26
+ 'SSID', // Google session
27
+ 'APISID',
28
+ 'SAPISID', // API auth
29
+ 'OSID',
30
+ '__Secure-OSID', // NotebookLM-specific
31
+ '__Secure-1PSID',
32
+ '__Secure-3PSID', // Secure variants
33
33
  ];
34
34
  export class AuthManager {
35
35
  stateFilePath;
36
36
  sessionFilePath;
37
37
  constructor() {
38
- this.stateFilePath = path.join(CONFIG.browserStateDir, "state.json");
39
- this.sessionFilePath = path.join(CONFIG.browserStateDir, "session.json");
38
+ this.stateFilePath = path.join(CONFIG.browserStateDir, 'state.json');
39
+ this.sessionFilePath = path.join(CONFIG.browserStateDir, 'session.json');
40
40
  }
41
41
  // ============================================================================
42
42
  // Browser State Management
@@ -66,7 +66,7 @@ export class AuthManager {
66
66
  return JSON.stringify(storage);
67
67
  });
68
68
  await fs.writeFile(this.sessionFilePath, sessionStorageData, {
69
- encoding: "utf-8",
69
+ encoding: 'utf-8',
70
70
  });
71
71
  const entries = Object.keys(JSON.parse(sessionStorageData)).length;
72
72
  log.success(`โœ… Browser state saved (incl. sessionStorage: ${entries} entries)`);
@@ -76,7 +76,7 @@ export class AuthManager {
76
76
  }
77
77
  }
78
78
  else {
79
- log.success("โœ… Browser state saved");
79
+ log.success('โœ… Browser state saved');
80
80
  }
81
81
  return true;
82
82
  }
@@ -116,8 +116,8 @@ export class AuthManager {
116
116
  return null;
117
117
  }
118
118
  if (await this.isStateExpired()) {
119
- log.warning("โš ๏ธ Saved state is expired (>24h old)");
120
- log.info("๐Ÿ’ก Run setup_auth tool to re-authenticate");
119
+ log.warning('โš ๏ธ Saved state is expired (>24h old)');
120
+ log.info('๐Ÿ’ก Run setup_auth tool to re-authenticate');
121
121
  return null;
122
122
  }
123
123
  return statePath;
@@ -127,7 +127,7 @@ export class AuthManager {
127
127
  */
128
128
  async loadSessionStorage() {
129
129
  try {
130
- const data = await fs.readFile(this.sessionFilePath, { encoding: "utf-8" });
130
+ const data = await fs.readFile(this.sessionFilePath, { encoding: 'utf-8' });
131
131
  const sessionData = JSON.parse(data);
132
132
  log.success(`โœ… Loaded sessionStorage (${Object.keys(sessionData).length} entries)`);
133
133
  return sessionData;
@@ -147,13 +147,13 @@ export class AuthManager {
147
147
  try {
148
148
  const cookies = await context.cookies();
149
149
  if (cookies.length === 0) {
150
- log.warning("โš ๏ธ No cookies found in state");
150
+ log.warning('โš ๏ธ No cookies found in state');
151
151
  return false;
152
152
  }
153
153
  // Check for Google auth cookies
154
- const googleCookies = cookies.filter((c) => c.domain.includes("google.com"));
154
+ const googleCookies = cookies.filter((c) => c.domain.includes('google.com'));
155
155
  if (googleCookies.length === 0) {
156
- log.warning("โš ๏ธ No Google cookies found");
156
+ log.warning('โš ๏ธ No Google cookies found');
157
157
  return false;
158
158
  }
159
159
  // Check if important cookies are expired
@@ -165,7 +165,7 @@ export class AuthManager {
165
165
  return false;
166
166
  }
167
167
  }
168
- log.success("โœ… State validation passed");
168
+ log.success('โœ… State validation passed');
169
169
  return true;
170
170
  }
171
171
  catch (error) {
@@ -180,13 +180,13 @@ export class AuthManager {
180
180
  try {
181
181
  const cookies = await context.cookies();
182
182
  if (cookies.length === 0) {
183
- log.warning("โš ๏ธ No cookies found");
183
+ log.warning('โš ๏ธ No cookies found');
184
184
  return false;
185
185
  }
186
186
  // Find critical cookies
187
187
  const criticalCookies = cookies.filter((c) => CRITICAL_COOKIE_NAMES.includes(c.name));
188
188
  if (criticalCookies.length === 0) {
189
- log.warning("โš ๏ธ No critical auth cookies found");
189
+ log.warning('โš ๏ธ No critical auth cookies found');
190
190
  return false;
191
191
  }
192
192
  // Check expiration for each critical cookie
@@ -204,7 +204,7 @@ export class AuthManager {
204
204
  }
205
205
  }
206
206
  if (expiredCookies.length > 0) {
207
- log.warning(`โš ๏ธ Expired cookies: ${expiredCookies.join(", ")}`);
207
+ log.warning(`โš ๏ธ Expired cookies: ${expiredCookies.join(', ')}`);
208
208
  return false;
209
209
  }
210
210
  log.success(`โœ… All ${criticalCookies.length} critical cookies are valid`);
@@ -245,18 +245,18 @@ export class AuthManager {
245
245
  */
246
246
  async performLogin(page, sendProgress) {
247
247
  try {
248
- log.info("๐ŸŒ Opening Google login page...");
249
- log.warning("๐Ÿ“ Please login to your Google account");
250
- log.warning("โณ Browser will close automatically once you reach NotebookLM");
251
- log.info("");
248
+ log.info('๐ŸŒ Opening Google login page...');
249
+ log.warning('๐Ÿ“ Please login to your Google account');
250
+ log.warning('โณ Browser will close automatically once you reach NotebookLM');
251
+ log.info('');
252
252
  // Progress: Navigating
253
- await sendProgress?.("Navigating to Google login...", 3, 10);
253
+ await sendProgress?.('Navigating to Google login...', 3, 10);
254
254
  // Navigate to Google login (redirects to NotebookLM after auth)
255
255
  await page.goto(NOTEBOOKLM_AUTH_URL, { timeout: 60000 });
256
256
  // Progress: Waiting for login
257
- await sendProgress?.("Waiting for manual login (up to 10 minutes)...", 4, 10);
257
+ await sendProgress?.('Waiting for manual login (up to 10 minutes)...', 4, 10);
258
258
  // Wait for user to complete login
259
- log.warning("โณ Waiting for login (up to 10 minutes)...");
259
+ log.warning('โณ Waiting for login (up to 10 minutes)...');
260
260
  const checkIntervalMs = 1000; // Check every 1 second
261
261
  const maxAttempts = 600; // 10 minutes total
262
262
  let lastProgressUpdate = 0;
@@ -271,34 +271,49 @@ export class AuthManager {
271
271
  await sendProgress?.(`Waiting for login... (${elapsedSeconds}s elapsed)`, progressStep, 10);
272
272
  }
273
273
  // โœ… SIMPLE: Check if we're on NotebookLM (any path!)
274
- if (currentUrl.startsWith("https://notebooklm.google.com/")) {
275
- await sendProgress?.("Login successful! NotebookLM detected!", 9, 10);
276
- log.success("โœ… Login successful! NotebookLM URL detected.");
274
+ if (currentUrl.startsWith('https://notebooklm.google.com/')) {
275
+ await sendProgress?.('Login successful! NotebookLM detected!', 9, 10);
276
+ log.success('โœ… Login successful! NotebookLM URL detected.');
277
277
  log.success(`โœ… Current URL: ${currentUrl}`);
278
- // Short wait to ensure page is loaded
279
- await page.waitForTimeout(2000);
278
+ // โœ… CRITICAL: Wait for page to fully load before saving cookies
279
+ // NotebookLM needs time to:
280
+ // 1. Complete the OAuth redirect
281
+ // 2. Generate session cookies (__Secure-OSID, etc.)
282
+ // 3. Load the application and establish session
283
+ log.info('โณ Waiting for NotebookLM to fully load (10 seconds)...');
284
+ await sendProgress?.('Waiting for page to fully load...', 9, 10);
285
+ // Wait for network to be idle (no more requests for 500ms)
286
+ try {
287
+ await page.waitForLoadState('networkidle', { timeout: 15000 });
288
+ log.success('โœ… Page network is idle');
289
+ }
290
+ catch {
291
+ log.warning('โš ๏ธ Network idle timeout - continuing anyway');
292
+ }
293
+ // Additional buffer to ensure all cookies are set
294
+ await page.waitForTimeout(5000);
295
+ log.success('โœ… Page fully loaded, cookies should be set');
280
296
  return true;
281
297
  }
282
298
  // Still on accounts.google.com - log periodically
283
- if (currentUrl.includes("accounts.google.com") && attempt % 30 === 0 && attempt > 0) {
299
+ if (currentUrl.includes('accounts.google.com') && attempt % 30 === 0 && attempt > 0) {
284
300
  log.warning(`โณ Still waiting... (${elapsedSeconds}s elapsed)`);
285
301
  }
286
302
  await page.waitForTimeout(checkIntervalMs);
287
303
  }
288
- catch (error) {
289
- log.debug(`[LOGIN] Poll iteration error (non-critical): ${error}`);
304
+ catch {
290
305
  await page.waitForTimeout(checkIntervalMs);
291
306
  continue;
292
307
  }
293
308
  }
294
309
  // Timeout reached - final check
295
310
  const currentUrl = page.url();
296
- if (currentUrl.startsWith("https://notebooklm.google.com/")) {
297
- await sendProgress?.("Login successful (detected on timeout check)!", 9, 10);
298
- log.success("โœ… Login successful (detected on timeout check)");
311
+ if (currentUrl.startsWith('https://notebooklm.google.com/')) {
312
+ await sendProgress?.('Login successful (detected on timeout check)!', 9, 10);
313
+ log.success('โœ… Login successful (detected on timeout check)');
299
314
  return true;
300
315
  }
301
- log.error("โŒ Login verification failed - timeout reached");
316
+ log.error('โŒ Login verification failed - timeout reached');
302
317
  log.warning(`Current URL: ${currentUrl}`);
303
318
  return false;
304
319
  }
@@ -318,55 +333,55 @@ export class AuthManager {
318
333
  log.warning(`๐Ÿ” Attempting automatic login for ${maskedEmail}...`);
319
334
  // Log browser visibility
320
335
  if (!CONFIG.headless) {
321
- log.info(" ๐Ÿ‘๏ธ Browser is VISIBLE for debugging");
336
+ log.info(' ๐Ÿ‘๏ธ Browser is VISIBLE for debugging');
322
337
  }
323
338
  else {
324
- log.info(" ๐Ÿ™ˆ Browser is HEADLESS (invisible)");
339
+ log.info(' ๐Ÿ™ˆ Browser is HEADLESS (invisible)');
325
340
  }
326
341
  log.info(` ๐ŸŒ Navigating to Google login...`);
327
342
  try {
328
343
  await page.goto(NOTEBOOKLM_AUTH_URL, {
329
- waitUntil: "domcontentloaded",
344
+ waitUntil: 'domcontentloaded',
330
345
  timeout: CONFIG.browserTimeout,
331
346
  });
332
347
  log.success(` โœ… Page loaded: ${page.url().slice(0, 80)}...`);
333
348
  }
334
- catch (error) {
349
+ catch {
335
350
  log.warning(` โš ๏ธ Page load timeout (continuing anyway)`);
336
351
  }
337
352
  const deadline = Date.now() + CONFIG.autoLoginTimeoutMs;
338
353
  log.info(` โฐ Auto-login timeout: ${CONFIG.autoLoginTimeoutMs / 1000}s`);
339
354
  // Already on NotebookLM?
340
- log.info(" ๐Ÿ” Checking if already authenticated...");
355
+ log.info(' ๐Ÿ” Checking if already authenticated...');
341
356
  if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
342
- log.success("โœ… Already authenticated");
357
+ log.success('โœ… Already authenticated');
343
358
  await this.saveBrowserState(context, page);
344
359
  return true;
345
360
  }
346
- log.warning(" โŒ Not authenticated yet, proceeding with login...");
361
+ log.warning(' โŒ Not authenticated yet, proceeding with login...');
347
362
  // Handle possible account chooser
348
- log.info(" ๐Ÿ” Checking for account chooser...");
363
+ log.info(' ๐Ÿ” Checking for account chooser...');
349
364
  if (await this.handleAccountChooser(page, email)) {
350
- log.success(" โœ… Account selected from chooser");
365
+ log.success(' โœ… Account selected from chooser');
351
366
  if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
352
- log.success("โœ… Automatic login successful");
367
+ log.success('โœ… Automatic login successful');
353
368
  await this.saveBrowserState(context, page);
354
369
  return true;
355
370
  }
356
371
  }
357
372
  // Email step
358
- log.info(" ๐Ÿ“ง Entering email address...");
373
+ log.info(' ๐Ÿ“ง Entering email address...');
359
374
  if (!(await this.fillIdentifier(page, email))) {
360
375
  if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
361
- log.success("โœ… Automatic login successful");
376
+ log.success('โœ… Automatic login successful');
362
377
  await this.saveBrowserState(context, page);
363
378
  return true;
364
379
  }
365
- log.warning("โš ๏ธ Email input not detected");
380
+ log.warning('โš ๏ธ Email input not detected');
366
381
  }
367
382
  // Password step (wait until visible)
368
383
  let waitAttempts = 0;
369
- log.warning(" โณ Waiting for password page to load...");
384
+ log.warning(' โณ Waiting for password page to load...');
370
385
  while (Date.now() < deadline && !(await this.fillPassword(page, password))) {
371
386
  waitAttempts++;
372
387
  // Log every 10 seconds (20 attempts * 0.5s)
@@ -376,21 +391,21 @@ export class AuthManager {
376
391
  log.warning(` โณ Still waiting for password field... (${secondsWaited}s elapsed, ${secondsRemaining.toFixed(0)}s remaining)`);
377
392
  log.info(` ๐Ÿ“ Current URL: ${page.url().slice(0, 100)}`);
378
393
  }
379
- if (page.url().includes("challenge")) {
380
- log.warning("โš ๏ธ Additional verification required (Google challenge page).");
394
+ if (page.url().includes('challenge')) {
395
+ log.warning('โš ๏ธ Additional verification required (Google challenge page).');
381
396
  return false;
382
397
  }
383
398
  await page.waitForTimeout(500);
384
399
  }
385
400
  // Wait for Google redirect after login
386
- log.info(" ๐Ÿ”„ Waiting for Google redirect to NotebookLM...");
401
+ log.info(' ๐Ÿ”„ Waiting for Google redirect to NotebookLM...');
387
402
  if (await this.waitForRedirectAfterLogin(page, deadline)) {
388
- log.success("โœ… Automatic login successful");
403
+ log.success('โœ… Automatic login successful');
389
404
  await this.saveBrowserState(context, page);
390
405
  return true;
391
406
  }
392
407
  // Login failed - diagnose
393
- log.error("โŒ Automatic login timed out");
408
+ log.error('โŒ Automatic login timed out');
394
409
  // Take screenshot for debugging
395
410
  try {
396
411
  const screenshotPath = path.join(CONFIG.dataDir, `login_failed_${Date.now()}.png`);
@@ -402,27 +417,27 @@ export class AuthManager {
402
417
  }
403
418
  // Diagnose specific failure reason
404
419
  const currentUrl = page.url();
405
- log.warning(" ๐Ÿ” Diagnosing failure...");
406
- if (currentUrl.includes("accounts.google.com")) {
407
- if (currentUrl.includes("/signin/identifier")) {
408
- log.error(" โŒ Still on email page - email input might have failed");
409
- log.info(" ๐Ÿ’ก Check if email is correct in .env");
420
+ log.warning(' ๐Ÿ” Diagnosing failure...');
421
+ if (currentUrl.includes('accounts.google.com')) {
422
+ if (currentUrl.includes('/signin/identifier')) {
423
+ log.error(' โŒ Still on email page - email input might have failed');
424
+ log.info(' ๐Ÿ’ก Check if email is correct in .env');
410
425
  }
411
- else if (currentUrl.includes("/challenge")) {
412
- log.error(" โŒ Google requires additional verification (2FA, CAPTCHA, suspicious login)");
413
- log.info(" ๐Ÿ’ก Try logging in manually first: use setup_auth tool");
426
+ else if (currentUrl.includes('/challenge')) {
427
+ log.error(' โŒ Google requires additional verification (2FA, CAPTCHA, suspicious login)');
428
+ log.info(' ๐Ÿ’ก Try logging in manually first: use setup_auth tool');
414
429
  }
415
- else if (currentUrl.includes("/pwd") || currentUrl.includes("/password")) {
416
- log.error(" โŒ Still on password page - password input might have failed");
417
- log.info(" ๐Ÿ’ก Check if password is correct in .env");
430
+ else if (currentUrl.includes('/pwd') || currentUrl.includes('/password')) {
431
+ log.error(' โŒ Still on password page - password input might have failed');
432
+ log.info(' ๐Ÿ’ก Check if password is correct in .env');
418
433
  }
419
434
  else {
420
435
  log.error(` โŒ Stuck on Google accounts page: ${currentUrl.slice(0, 80)}...`);
421
436
  }
422
437
  }
423
- else if (currentUrl.includes("notebooklm.google.com")) {
438
+ else if (currentUrl.includes('notebooklm.google.com')) {
424
439
  log.warning(" โš ๏ธ Reached NotebookLM but couldn't detect successful login");
425
- log.info(" ๐Ÿ’ก This might be a timing issue - try again");
440
+ log.info(' ๐Ÿ’ก This might be a timing issue - try again');
426
441
  }
427
442
  else {
428
443
  log.error(` โŒ Unexpected page: ${currentUrl.slice(0, 80)}...`);
@@ -439,24 +454,24 @@ export class AuthManager {
439
454
  * Matches the simplified approach used in performLogin().
440
455
  */
441
456
  async waitForRedirectAfterLogin(page, deadline) {
442
- log.info(" โณ Waiting for redirect to NotebookLM...");
457
+ log.info(' โณ Waiting for redirect to NotebookLM...');
443
458
  while (Date.now() < deadline) {
444
459
  try {
445
460
  const currentUrl = page.url();
446
461
  // Simple check: Are we on NotebookLM?
447
- if (currentUrl.startsWith("https://notebooklm.google.com/")) {
448
- log.success(" โœ… NotebookLM URL detected!");
462
+ if (currentUrl.startsWith('https://notebooklm.google.com/')) {
463
+ log.success(' โœ… NotebookLM URL detected!');
449
464
  // Short wait to ensure page is loaded
450
465
  await page.waitForTimeout(2000);
451
466
  return true;
452
467
  }
453
468
  }
454
- catch (error) {
455
- log.debug(`[REDIRECT] URL check error (non-critical): ${error}`);
469
+ catch {
470
+ // Ignore errors
456
471
  }
457
472
  await page.waitForTimeout(500);
458
473
  }
459
- log.error(" โŒ Redirect timeout - NotebookLM URL not reached");
474
+ log.error(' โŒ Redirect timeout - NotebookLM URL not reached');
460
475
  return false;
461
476
  }
462
477
  /**
@@ -471,13 +486,13 @@ export class AuthManager {
471
486
  try {
472
487
  const currentUrl = page.url();
473
488
  // Simple check: Are we on NotebookLM?
474
- if (currentUrl.startsWith("https://notebooklm.google.com/")) {
475
- log.success(" โœ… NotebookLM URL detected");
489
+ if (currentUrl.startsWith('https://notebooklm.google.com/')) {
490
+ log.success(' โœ… NotebookLM URL detected');
476
491
  return true;
477
492
  }
478
493
  }
479
- catch (error) {
480
- log.debug(`[NOTEBOOK] URL check error (non-critical): ${error}`);
494
+ catch {
495
+ // Ignore errors
481
496
  }
482
497
  await page.waitForTimeout(1000);
483
498
  }
@@ -488,10 +503,10 @@ export class AuthManager {
488
503
  */
489
504
  async handleAccountChooser(page, email) {
490
505
  try {
491
- const chooser = await page.$$("div[data-identifier], li[data-identifier]");
506
+ const chooser = await page.$$('div[data-identifier], li[data-identifier]');
492
507
  if (chooser.length > 0) {
493
508
  for (const item of chooser) {
494
- const identifier = (await item.getAttribute("data-identifier"))?.toLowerCase() || "";
509
+ const identifier = (await item.getAttribute('data-identifier'))?.toLowerCase() || '';
495
510
  if (identifier === email.toLowerCase()) {
496
511
  await item.click();
497
512
  await randomDelay(150, 320);
@@ -501,17 +516,16 @@ export class AuthManager {
501
516
  }
502
517
  // Click "Use another account"
503
518
  await this.clickText(page, [
504
- "Use another account",
505
- "Weiteres Konto hinzufรผgen",
506
- "Anderes Konto verwenden",
519
+ 'Use another account',
520
+ 'Weiteres Konto hinzufรผgen',
521
+ 'Anderes Konto verwenden',
507
522
  ]);
508
523
  await randomDelay(150, 320);
509
524
  return false;
510
525
  }
511
526
  return false;
512
527
  }
513
- catch (error) {
514
- log.debug(`[ACCOUNT_CHOOSER] Error handling account chooser: ${error}`);
528
+ catch {
515
529
  return false;
516
530
  }
517
531
  }
@@ -519,9 +533,9 @@ export class AuthManager {
519
533
  * Fill email identifier field with human-like typing
520
534
  */
521
535
  async fillIdentifier(page, email) {
522
- log.info(" ๐Ÿ“ง Looking for email field...");
536
+ log.info(' ๐Ÿ“ง Looking for email field...');
523
537
  const emailSelectors = [
524
- "input#identifierId",
538
+ 'input#identifierId',
525
539
  "input[name='identifier']",
526
540
  "input[type='email']",
527
541
  ];
@@ -530,7 +544,7 @@ export class AuthManager {
530
544
  for (const selector of emailSelectors) {
531
545
  try {
532
546
  const candidate = await page.waitForSelector(selector, {
533
- state: "attached",
547
+ state: 'attached',
534
548
  timeout: 3000,
535
549
  });
536
550
  if (!candidate)
@@ -540,8 +554,7 @@ export class AuthManager {
540
554
  continue; // Hidden field
541
555
  }
542
556
  }
543
- catch (error) {
544
- log.debug(`[EMAIL] Visibility check failed for ${selector}: ${error}`);
557
+ catch {
545
558
  continue;
546
559
  }
547
560
  emailField = candidate;
@@ -549,13 +562,12 @@ export class AuthManager {
549
562
  log.success(` โœ… Email field visible: ${selector}`);
550
563
  break;
551
564
  }
552
- catch (error) {
553
- log.debug(`[EMAIL] Selector ${selector} not found: ${error}`);
565
+ catch {
554
566
  continue;
555
567
  }
556
568
  }
557
569
  if (!emailField || !emailSelector) {
558
- log.warning(" โ„น๏ธ No visible email field found (likely pre-filled)");
570
+ log.warning(' โ„น๏ธ No visible email field found (likely pre-filled)');
559
571
  log.info(` ๐Ÿ“ Current URL: ${page.url().slice(0, 100)}`);
560
572
  return false;
561
573
  }
@@ -569,8 +581,8 @@ export class AuthManager {
569
581
  await randomDelay(200, 500);
570
582
  }
571
583
  }
572
- catch (error) {
573
- log.debug(`[EMAIL] Mouse movement failed (non-critical): ${error}`);
584
+ catch {
585
+ // Ignore errors
574
586
  }
575
587
  // Click to focus
576
588
  try {
@@ -581,37 +593,37 @@ export class AuthManager {
581
593
  try {
582
594
  await emailField.focus();
583
595
  }
584
- catch (focusError) {
585
- log.error(` โŒ Failed to focus email field: ${focusError}`);
596
+ catch {
597
+ log.error(' โŒ Failed to focus email field');
586
598
  return false;
587
599
  }
588
600
  }
589
601
  // โœ… FASTER: Programmer typing speed (90-120 WPM from config)
590
602
  log.info(` โŒจ๏ธ Typing email: ${this.maskEmail(email)}`);
591
603
  try {
592
- const wpm = CONFIG.typingWpmMin + Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
604
+ const wpm = CONFIG.typingWpmMin +
605
+ Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
593
606
  await humanType(page, emailSelector, email, { wpm, withTypos: false });
594
- log.success(" โœ… Email typed successfully");
607
+ log.success(' โœ… Email typed successfully');
595
608
  }
596
609
  catch (error) {
597
610
  log.error(` โŒ Typing failed: ${error}`);
598
611
  try {
599
612
  await page.fill(emailSelector, email);
600
- log.success(" โœ… Filled email using fallback");
613
+ log.success(' โœ… Filled email using fallback');
601
614
  }
602
- catch (fillError) {
603
- log.error(` โŒ Fallback fill also failed: ${fillError}`);
615
+ catch {
604
616
  return false;
605
617
  }
606
618
  }
607
619
  // Human "thinking" pause before clicking Next
608
620
  await randomDelay(400, 1200);
609
621
  // Click Next button
610
- log.info(" ๐Ÿ”˜ Looking for Next button...");
622
+ log.info(' ๐Ÿ”˜ Looking for Next button...');
611
623
  const nextSelectors = [
612
624
  "button:has-text('Next')",
613
625
  "button:has-text('Weiter')",
614
- "#identifierNext",
626
+ '#identifierNext',
615
627
  ];
616
628
  let nextClicked = false;
617
629
  for (const selector of nextSelectors) {
@@ -624,25 +636,24 @@ export class AuthManager {
624
636
  break;
625
637
  }
626
638
  }
627
- catch (error) {
628
- log.debug(`[EMAIL] Next button selector ${selector} failed: ${error}`);
639
+ catch {
629
640
  continue;
630
641
  }
631
642
  }
632
643
  if (!nextClicked) {
633
- log.warning(" โš ๏ธ Button not found, pressing Enter");
634
- await emailField.press("Enter");
644
+ log.warning(' โš ๏ธ Button not found, pressing Enter');
645
+ await emailField.press('Enter');
635
646
  }
636
647
  // Variable delay
637
648
  await randomDelay(800, 1500);
638
- log.success(" โœ… Email step complete");
649
+ log.success(' โœ… Email step complete');
639
650
  return true;
640
651
  }
641
652
  /**
642
653
  * Fill password field with human-like typing
643
654
  */
644
655
  async fillPassword(page, password) {
645
- log.info(" ๐Ÿ” Looking for password field...");
656
+ log.info(' ๐Ÿ” Looking for password field...');
646
657
  const passwordSelectors = ["input[name='Passwd']", "input[type='password']"];
647
658
  let passwordSelector = null;
648
659
  let passwordField = null;
@@ -655,8 +666,7 @@ export class AuthManager {
655
666
  break;
656
667
  }
657
668
  }
658
- catch (error) {
659
- log.debug(`[PASSWORD] Selector ${selector} not found: ${error}`);
669
+ catch {
660
670
  continue;
661
671
  }
662
672
  }
@@ -674,21 +684,22 @@ export class AuthManager {
674
684
  await randomDelay(300, 700);
675
685
  }
676
686
  }
677
- catch (error) {
678
- log.debug(`[PASSWORD] Mouse movement failed (non-critical): ${error}`);
687
+ catch {
688
+ // Ignore errors
679
689
  }
680
690
  // Click to focus
681
691
  if (passwordSelector) {
682
692
  await realisticClick(page, passwordSelector, false);
683
693
  }
684
694
  // โœ… FASTER: Programmer typing speed (90-120 WPM from config)
685
- log.info(" โŒจ๏ธ Typing password...");
695
+ log.info(' โŒจ๏ธ Typing password...');
686
696
  try {
687
- const wpm = CONFIG.typingWpmMin + Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
697
+ const wpm = CONFIG.typingWpmMin +
698
+ Math.floor(Math.random() * (CONFIG.typingWpmMax - CONFIG.typingWpmMin + 1));
688
699
  if (passwordSelector) {
689
700
  await humanType(page, passwordSelector, password, { wpm, withTypos: false });
690
701
  }
691
- log.success(" โœ… Password typed successfully");
702
+ log.success(' โœ… Password typed successfully');
692
703
  }
693
704
  catch (error) {
694
705
  log.error(` โŒ Typing failed: ${error}`);
@@ -697,11 +708,11 @@ export class AuthManager {
697
708
  // Human "review" pause before submitting password
698
709
  await randomDelay(300, 1000);
699
710
  // Click Next button
700
- log.info(" ๐Ÿ”˜ Looking for Next button...");
711
+ log.info(' ๐Ÿ”˜ Looking for Next button...');
701
712
  const pwdNextSelectors = [
702
713
  "button:has-text('Next')",
703
714
  "button:has-text('Weiter')",
704
- "#passwordNext",
715
+ '#passwordNext',
705
716
  ];
706
717
  let pwdNextClicked = false;
707
718
  for (const selector of pwdNextSelectors) {
@@ -714,18 +725,17 @@ export class AuthManager {
714
725
  break;
715
726
  }
716
727
  }
717
- catch (error) {
718
- log.debug(`[PASSWORD] Next button selector ${selector} failed: ${error}`);
728
+ catch {
719
729
  continue;
720
730
  }
721
731
  }
722
732
  if (!pwdNextClicked) {
723
- log.warning(" โš ๏ธ Button not found, pressing Enter");
724
- await passwordField.press("Enter");
733
+ log.warning(' โš ๏ธ Button not found, pressing Enter');
734
+ await passwordField.press('Enter');
725
735
  }
726
736
  // Variable delay
727
737
  await randomDelay(800, 1500);
728
- log.success(" โœ… Password step complete");
738
+ log.success(' โœ… Password step complete');
729
739
  return true;
730
740
  }
731
741
  /**
@@ -742,8 +752,7 @@ export class AuthManager {
742
752
  return true;
743
753
  }
744
754
  }
745
- catch (error) {
746
- log.debug(`[CLICK_TEXT] Text "${text}" not found or click failed: ${error}`);
755
+ catch {
747
756
  continue;
748
757
  }
749
758
  }
@@ -753,14 +762,14 @@ export class AuthManager {
753
762
  * Mask email for logging
754
763
  */
755
764
  maskEmail(email) {
756
- if (!email.includes("@")) {
757
- return "***";
765
+ if (!email.includes('@')) {
766
+ return '***';
758
767
  }
759
- const [name, domain] = email.split("@");
768
+ const [name, domain] = email.split('@');
760
769
  if (name.length <= 2) {
761
- return `${"*".repeat(name.length)}@${domain}`;
770
+ return `${'*'.repeat(name.length)}@${domain}`;
762
771
  }
763
- return `${name[0]}${"*".repeat(name.length - 2)}${name[name.length - 1]}@${domain}`;
772
+ return `${name[0]}${'*'.repeat(name.length - 2)}${name[name.length - 1]}@${domain}`;
764
773
  }
765
774
  // ============================================================================
766
775
  // Additional Helper Methods
@@ -771,7 +780,7 @@ export class AuthManager {
771
780
  async loadAuthState(context, statePath) {
772
781
  try {
773
782
  // Read state.json
774
- const stateData = await fs.readFile(statePath, { encoding: "utf-8" });
783
+ const stateData = await fs.readFile(statePath, { encoding: 'utf-8' });
775
784
  const state = JSON.parse(stateData);
776
785
  // Add cookies to context
777
786
  if (state.cookies) {
@@ -805,7 +814,7 @@ export class AuthManager {
805
814
  * If not provided, defaults to true (visible) for setup
806
815
  */
807
816
  async performSetup(sendProgress, overrideHeadless) {
808
- const { chromium } = await import("patchright");
817
+ const { chromium } = await import('patchright');
809
818
  // Determine headless mode: override or default to true (visible for setup)
810
819
  // overrideHeadless contains show_browser value (true = show, false = hide)
811
820
  const shouldShowBrowser = overrideHeadless !== undefined ? overrideHeadless : true;
@@ -814,29 +823,29 @@ export class AuthManager {
814
823
  const statePath = await this.getValidStatePath();
815
824
  const isAuthenticated = statePath !== null;
816
825
  if (isAuthenticated) {
817
- log.info("โœ… Already authenticated, skipping setup");
826
+ log.info('โœ… Already authenticated, skipping setup');
818
827
  log.info(" Use 're_auth' tool to switch accounts or re-authenticate");
819
- await sendProgress?.("Already authenticated!", 10, 10);
828
+ await sendProgress?.('Already authenticated!', 10, 10);
820
829
  return true;
821
830
  }
822
- log.info("๐Ÿ”„ Preparing for first-time authentication...");
823
- await sendProgress?.("Preparing browser...", 1, 10);
824
- log.info("๐Ÿš€ Launching persistent browser for interactive setup...");
831
+ log.info('๐Ÿ”„ Preparing for first-time authentication...');
832
+ await sendProgress?.('Preparing browser...', 1, 10);
833
+ log.info('๐Ÿš€ Launching persistent browser for interactive setup...');
825
834
  log.info(` ๐Ÿ“ Profile: ${CONFIG.chromeProfileDir}`);
826
- await sendProgress?.("Launching persistent browser...", 2, 10);
835
+ await sendProgress?.('Launching persistent browser...', 2, 10);
827
836
  // โœ… CRITICAL FIX: Use launchPersistentContext (same as runtime!)
828
837
  // This ensures session cookies persist correctly
829
838
  const context = await chromium.launchPersistentContext(CONFIG.chromeProfileDir, {
830
839
  headless: !shouldShowBrowser, // Use override or default to visible for setup
831
- channel: "chrome",
840
+ channel: 'chrome',
832
841
  viewport: CONFIG.viewport,
833
- locale: "en-US",
834
- timezoneId: "Europe/Berlin",
842
+ locale: 'en-US',
843
+ timezoneId: 'Europe/Berlin',
835
844
  args: [
836
- "--disable-blink-features=AutomationControlled",
837
- "--disable-dev-shm-usage",
838
- "--no-first-run",
839
- "--no-default-browser-check",
845
+ '--disable-blink-features=AutomationControlled',
846
+ '--disable-dev-shm-usage',
847
+ '--no-first-run',
848
+ '--no-default-browser-check',
840
849
  ],
841
850
  });
842
851
  // Get or create a page
@@ -847,17 +856,17 @@ export class AuthManager {
847
856
  if (loginSuccess) {
848
857
  // โœ… Save browser state to state.json (for validation & backup)
849
858
  // Chrome ALSO saves everything to the persistent profile automatically!
850
- await sendProgress?.("Saving authentication state...", 9, 10);
859
+ await sendProgress?.('Saving authentication state...', 9, 10);
851
860
  await this.saveBrowserState(context, page);
852
861
  // โœ… CRITICAL FIX: Wait for Chrome to flush profile to disk
853
862
  // Windows needs time to write persistent data (cookies, cache, session storage, etc.)
854
863
  // Without this delay, chrome_profile/ folder remains empty!
855
- log.info("โณ Waiting for Chrome to finalize profile writes (5 seconds)...");
864
+ log.info('โณ Waiting for Chrome to finalize profile writes (5 seconds)...');
856
865
  await page.waitForTimeout(5000); // 5 seconds buffer for Windows filesystem
857
- log.success("โœ… Setup complete - authentication saved to:");
866
+ log.success('โœ… Setup complete - authentication saved to:');
858
867
  log.success(` ๐Ÿ“„ State file: ${this.stateFilePath}`);
859
868
  log.success(` ๐Ÿ“ Chrome profile: ${CONFIG.chromeProfileDir}`);
860
- log.info("๐Ÿ’ก Session cookies will now persist across restarts!");
869
+ log.info('๐Ÿ’ก Session cookies will now persist across restarts!');
861
870
  }
862
871
  // Close persistent context
863
872
  await context.close();
@@ -882,13 +891,13 @@ export class AuthManager {
882
891
  * Use this BEFORE authenticating a new account!
883
892
  */
884
893
  async clearAllAuthData() {
885
- log.warning("๐Ÿ—‘๏ธ Clearing ALL authentication data for account switch...");
894
+ log.warning('๐Ÿ—‘๏ธ Clearing ALL authentication data for account switch...');
886
895
  let deletedCount = 0;
887
896
  // 1. Delete all state files in browser_state_dir
888
897
  try {
889
898
  const files = await fs.readdir(CONFIG.browserStateDir);
890
899
  for (const file of files) {
891
- if (file.endsWith(".json")) {
900
+ if (file.endsWith('.json')) {
892
901
  await fs.unlink(path.join(CONFIG.browserStateDir, file));
893
902
  log.info(` โœ… Deleted: ${file}`);
894
903
  deletedCount++;
@@ -912,7 +921,7 @@ export class AuthManager {
912
921
  log.warning(` โš ๏ธ Could not delete Chrome profile: ${error}`);
913
922
  }
914
923
  if (deletedCount === 0) {
915
- log.info(" โ„น๏ธ No old auth data found (already clean)");
924
+ log.info(' โ„น๏ธ No old auth data found (already clean)');
916
925
  }
917
926
  else {
918
927
  log.success(`โœ… All auth data cleared (${deletedCount} items) - ready for new account!`);
@@ -925,19 +934,17 @@ export class AuthManager {
925
934
  try {
926
935
  try {
927
936
  await fs.unlink(this.stateFilePath);
928
- log.debug(`[CLEAR_STATE] Deleted state file: ${this.stateFilePath}`);
929
937
  }
930
- catch (error) {
931
- log.debug(`[CLEAR_STATE] State file not found (expected): ${error}`);
938
+ catch {
939
+ // File doesn't exist
932
940
  }
933
941
  try {
934
942
  await fs.unlink(this.sessionFilePath);
935
- log.debug(`[CLEAR_STATE] Deleted session file: ${this.sessionFilePath}`);
936
943
  }
937
- catch (error) {
938
- log.debug(`[CLEAR_STATE] Session file not found (expected): ${error}`);
944
+ catch {
945
+ // File doesn't exist
939
946
  }
940
- log.success("โœ… Authentication state cleared");
947
+ log.success('โœ… Authentication state cleared');
941
948
  return true;
942
949
  }
943
950
  catch (error) {
@@ -950,7 +957,7 @@ export class AuthManager {
950
957
  */
951
958
  async hardResetState() {
952
959
  try {
953
- log.warning("๐Ÿงน Performing HARD RESET of all authentication state...");
960
+ log.warning('๐Ÿงน Performing HARD RESET of all authentication state...');
954
961
  let deletedCount = 0;
955
962
  // Delete state file
956
963
  try {
@@ -958,8 +965,8 @@ export class AuthManager {
958
965
  log.info(` ๐Ÿ—‘๏ธ Deleted: ${this.stateFilePath}`);
959
966
  deletedCount++;
960
967
  }
961
- catch (error) {
962
- log.debug(`[HARD_RESET] State file not found (expected): ${error}`);
968
+ catch {
969
+ // File doesn't exist
963
970
  }
964
971
  // Delete session file
965
972
  try {
@@ -967,8 +974,8 @@ export class AuthManager {
967
974
  log.info(` ๐Ÿ—‘๏ธ Deleted: ${this.sessionFilePath}`);
968
975
  deletedCount++;
969
976
  }
970
- catch (error) {
971
- log.debug(`[HARD_RESET] Session file not found (expected): ${error}`);
977
+ catch {
978
+ // File doesn't exist
972
979
  }
973
980
  // Delete entire browser_state_dir
974
981
  try {
@@ -979,11 +986,11 @@ export class AuthManager {
979
986
  }
980
987
  log.info(` ๐Ÿ—‘๏ธ Deleted: ${CONFIG.browserStateDir}/ (${files.length} files)`);
981
988
  }
982
- catch (error) {
983
- log.debug(`[HARD_RESET] Browser state dir empty or not found: ${error}`);
989
+ catch {
990
+ // Directory doesn't exist or empty
984
991
  }
985
992
  if (deletedCount === 0) {
986
- log.info(" โ„น๏ธ No state to delete (already clean)");
993
+ log.info(' โ„น๏ธ No state to delete (already clean)');
987
994
  }
988
995
  else {
989
996
  log.success(`โœ… Hard reset complete: ${deletedCount} items deleted`);