@roomi-fields/notebooklm-mcp 1.3.4 → 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 (96) 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 +147 -145
  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.js +19 -19
  46. package/dist/cli/help.js +60 -60
  47. package/dist/cli/setup-auth.js +39 -39
  48. package/dist/config.d.ts +1 -1
  49. package/dist/config.d.ts.map +1 -1
  50. package/dist/config.js +35 -20
  51. package/dist/config.js.map +1 -1
  52. package/dist/errors.d.ts.map +1 -1
  53. package/dist/errors.js +3 -3
  54. package/dist/errors.js.map +1 -1
  55. package/dist/http-wrapper.js +36 -30
  56. package/dist/http-wrapper.js.map +1 -1
  57. package/dist/index.js +104 -102
  58. package/dist/index.js.map +1 -1
  59. package/dist/library/notebook-library.d.ts +2 -2
  60. package/dist/library/notebook-library.d.ts.map +1 -1
  61. package/dist/library/notebook-library.js +24 -24
  62. package/dist/library/notebook-library.js.map +1 -1
  63. package/dist/session/browser-session.d.ts +4 -4
  64. package/dist/session/browser-session.d.ts.map +1 -1
  65. package/dist/session/browser-session.js +59 -55
  66. package/dist/session/browser-session.js.map +1 -1
  67. package/dist/session/session-manager.d.ts +3 -3
  68. package/dist/session/session-manager.d.ts.map +1 -1
  69. package/dist/session/session-manager.js +14 -14
  70. package/dist/session/session-manager.js.map +1 -1
  71. package/dist/session/shared-context-manager.d.ts +2 -2
  72. package/dist/session/shared-context-manager.d.ts.map +1 -1
  73. package/dist/session/shared-context-manager.js +60 -56
  74. package/dist/session/shared-context-manager.js.map +1 -1
  75. package/dist/tools/index.d.ts +6 -6
  76. package/dist/tools/index.d.ts.map +1 -1
  77. package/dist/tools/index.js +505 -505
  78. package/dist/tools/index.js.map +1 -1
  79. package/dist/types.d.ts +2 -2
  80. package/dist/utils/cleanup-manager.d.ts +1 -1
  81. package/dist/utils/cleanup-manager.d.ts.map +1 -1
  82. package/dist/utils/cleanup-manager.js +90 -92
  83. package/dist/utils/cleanup-manager.js.map +1 -1
  84. package/dist/utils/logger.d.ts +1 -2
  85. package/dist/utils/logger.d.ts.map +1 -1
  86. package/dist/utils/logger.js +15 -15
  87. package/dist/utils/page-utils.d.ts +1 -1
  88. package/dist/utils/page-utils.d.ts.map +1 -1
  89. package/dist/utils/page-utils.js +30 -30
  90. package/dist/utils/page-utils.js.map +1 -1
  91. package/dist/utils/stealth-utils.d.ts +2 -2
  92. package/dist/utils/stealth-utils.d.ts.map +1 -1
  93. package/dist/utils/stealth-utils.js +13 -13
  94. package/dist/utils/stealth-utils.js.map +1 -1
  95. package/docs/CHROME_PROFILE_LIMITATION.md +229 -212
  96. package/package.json +107 -79
@@ -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,32 +271,32 @@ 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
278
  // ✅ CRITICAL: Wait for page to fully load before saving cookies
279
279
  // NotebookLM needs time to:
280
280
  // 1. Complete the OAuth redirect
281
281
  // 2. Generate session cookies (__Secure-OSID, etc.)
282
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);
283
+ log.info('⏳ Waiting for NotebookLM to fully load (10 seconds)...');
284
+ await sendProgress?.('Waiting for page to fully load...', 9, 10);
285
285
  // Wait for network to be idle (no more requests for 500ms)
286
286
  try {
287
- await page.waitForLoadState("networkidle", { timeout: 15000 });
288
- log.success("✅ Page network is idle");
287
+ await page.waitForLoadState('networkidle', { timeout: 15000 });
288
+ log.success('✅ Page network is idle');
289
289
  }
290
290
  catch {
291
- log.warning("⚠️ Network idle timeout - continuing anyway");
291
+ log.warning('⚠️ Network idle timeout - continuing anyway');
292
292
  }
293
293
  // Additional buffer to ensure all cookies are set
294
294
  await page.waitForTimeout(5000);
295
- log.success("✅ Page fully loaded, cookies should be set");
295
+ log.success('✅ Page fully loaded, cookies should be set');
296
296
  return true;
297
297
  }
298
298
  // Still on accounts.google.com - log periodically
299
- if (currentUrl.includes("accounts.google.com") && attempt % 30 === 0 && attempt > 0) {
299
+ if (currentUrl.includes('accounts.google.com') && attempt % 30 === 0 && attempt > 0) {
300
300
  log.warning(`⏳ Still waiting... (${elapsedSeconds}s elapsed)`);
301
301
  }
302
302
  await page.waitForTimeout(checkIntervalMs);
@@ -308,12 +308,12 @@ export class AuthManager {
308
308
  }
309
309
  // Timeout reached - final check
310
310
  const currentUrl = page.url();
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)");
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)');
314
314
  return true;
315
315
  }
316
- log.error("❌ Login verification failed - timeout reached");
316
+ log.error('❌ Login verification failed - timeout reached');
317
317
  log.warning(`Current URL: ${currentUrl}`);
318
318
  return false;
319
319
  }
@@ -333,55 +333,55 @@ export class AuthManager {
333
333
  log.warning(`🔁 Attempting automatic login for ${maskedEmail}...`);
334
334
  // Log browser visibility
335
335
  if (!CONFIG.headless) {
336
- log.info(" 👁️ Browser is VISIBLE for debugging");
336
+ log.info(' 👁️ Browser is VISIBLE for debugging');
337
337
  }
338
338
  else {
339
- log.info(" 🙈 Browser is HEADLESS (invisible)");
339
+ log.info(' 🙈 Browser is HEADLESS (invisible)');
340
340
  }
341
341
  log.info(` 🌐 Navigating to Google login...`);
342
342
  try {
343
343
  await page.goto(NOTEBOOKLM_AUTH_URL, {
344
- waitUntil: "domcontentloaded",
344
+ waitUntil: 'domcontentloaded',
345
345
  timeout: CONFIG.browserTimeout,
346
346
  });
347
347
  log.success(` ✅ Page loaded: ${page.url().slice(0, 80)}...`);
348
348
  }
349
- catch (error) {
349
+ catch {
350
350
  log.warning(` ⚠️ Page load timeout (continuing anyway)`);
351
351
  }
352
352
  const deadline = Date.now() + CONFIG.autoLoginTimeoutMs;
353
353
  log.info(` ⏰ Auto-login timeout: ${CONFIG.autoLoginTimeoutMs / 1000}s`);
354
354
  // Already on NotebookLM?
355
- log.info(" 🔍 Checking if already authenticated...");
355
+ log.info(' 🔍 Checking if already authenticated...');
356
356
  if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
357
- log.success("✅ Already authenticated");
357
+ log.success('✅ Already authenticated');
358
358
  await this.saveBrowserState(context, page);
359
359
  return true;
360
360
  }
361
- log.warning(" ❌ Not authenticated yet, proceeding with login...");
361
+ log.warning(' ❌ Not authenticated yet, proceeding with login...');
362
362
  // Handle possible account chooser
363
- log.info(" 🔍 Checking for account chooser...");
363
+ log.info(' 🔍 Checking for account chooser...');
364
364
  if (await this.handleAccountChooser(page, email)) {
365
- log.success(" ✅ Account selected from chooser");
365
+ log.success(' ✅ Account selected from chooser');
366
366
  if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
367
- log.success("✅ Automatic login successful");
367
+ log.success('✅ Automatic login successful');
368
368
  await this.saveBrowserState(context, page);
369
369
  return true;
370
370
  }
371
371
  }
372
372
  // Email step
373
- log.info(" 📧 Entering email address...");
373
+ log.info(' 📧 Entering email address...');
374
374
  if (!(await this.fillIdentifier(page, email))) {
375
375
  if (await this.waitForNotebook(page, CONFIG.autoLoginTimeoutMs)) {
376
- log.success("✅ Automatic login successful");
376
+ log.success('✅ Automatic login successful');
377
377
  await this.saveBrowserState(context, page);
378
378
  return true;
379
379
  }
380
- log.warning("⚠️ Email input not detected");
380
+ log.warning('⚠️ Email input not detected');
381
381
  }
382
382
  // Password step (wait until visible)
383
383
  let waitAttempts = 0;
384
- log.warning(" ⏳ Waiting for password page to load...");
384
+ log.warning(' ⏳ Waiting for password page to load...');
385
385
  while (Date.now() < deadline && !(await this.fillPassword(page, password))) {
386
386
  waitAttempts++;
387
387
  // Log every 10 seconds (20 attempts * 0.5s)
@@ -391,21 +391,21 @@ export class AuthManager {
391
391
  log.warning(` ⏳ Still waiting for password field... (${secondsWaited}s elapsed, ${secondsRemaining.toFixed(0)}s remaining)`);
392
392
  log.info(` 📍 Current URL: ${page.url().slice(0, 100)}`);
393
393
  }
394
- if (page.url().includes("challenge")) {
395
- log.warning("⚠️ Additional verification required (Google challenge page).");
394
+ if (page.url().includes('challenge')) {
395
+ log.warning('⚠️ Additional verification required (Google challenge page).');
396
396
  return false;
397
397
  }
398
398
  await page.waitForTimeout(500);
399
399
  }
400
400
  // Wait for Google redirect after login
401
- log.info(" 🔄 Waiting for Google redirect to NotebookLM...");
401
+ log.info(' 🔄 Waiting for Google redirect to NotebookLM...');
402
402
  if (await this.waitForRedirectAfterLogin(page, deadline)) {
403
- log.success("✅ Automatic login successful");
403
+ log.success('✅ Automatic login successful');
404
404
  await this.saveBrowserState(context, page);
405
405
  return true;
406
406
  }
407
407
  // Login failed - diagnose
408
- log.error("❌ Automatic login timed out");
408
+ log.error('❌ Automatic login timed out');
409
409
  // Take screenshot for debugging
410
410
  try {
411
411
  const screenshotPath = path.join(CONFIG.dataDir, `login_failed_${Date.now()}.png`);
@@ -417,27 +417,27 @@ export class AuthManager {
417
417
  }
418
418
  // Diagnose specific failure reason
419
419
  const currentUrl = page.url();
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");
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');
425
425
  }
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");
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');
429
429
  }
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");
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');
433
433
  }
434
434
  else {
435
435
  log.error(` ❌ Stuck on Google accounts page: ${currentUrl.slice(0, 80)}...`);
436
436
  }
437
437
  }
438
- else if (currentUrl.includes("notebooklm.google.com")) {
438
+ else if (currentUrl.includes('notebooklm.google.com')) {
439
439
  log.warning(" ⚠️ Reached NotebookLM but couldn't detect successful login");
440
- log.info(" 💡 This might be a timing issue - try again");
440
+ log.info(' 💡 This might be a timing issue - try again');
441
441
  }
442
442
  else {
443
443
  log.error(` ❌ Unexpected page: ${currentUrl.slice(0, 80)}...`);
@@ -454,13 +454,13 @@ export class AuthManager {
454
454
  * Matches the simplified approach used in performLogin().
455
455
  */
456
456
  async waitForRedirectAfterLogin(page, deadline) {
457
- log.info(" ⏳ Waiting for redirect to NotebookLM...");
457
+ log.info(' ⏳ Waiting for redirect to NotebookLM...');
458
458
  while (Date.now() < deadline) {
459
459
  try {
460
460
  const currentUrl = page.url();
461
461
  // Simple check: Are we on NotebookLM?
462
- if (currentUrl.startsWith("https://notebooklm.google.com/")) {
463
- log.success(" ✅ NotebookLM URL detected!");
462
+ if (currentUrl.startsWith('https://notebooklm.google.com/')) {
463
+ log.success(' ✅ NotebookLM URL detected!');
464
464
  // Short wait to ensure page is loaded
465
465
  await page.waitForTimeout(2000);
466
466
  return true;
@@ -471,7 +471,7 @@ export class AuthManager {
471
471
  }
472
472
  await page.waitForTimeout(500);
473
473
  }
474
- log.error(" ❌ Redirect timeout - NotebookLM URL not reached");
474
+ log.error(' ❌ Redirect timeout - NotebookLM URL not reached');
475
475
  return false;
476
476
  }
477
477
  /**
@@ -486,8 +486,8 @@ export class AuthManager {
486
486
  try {
487
487
  const currentUrl = page.url();
488
488
  // Simple check: Are we on NotebookLM?
489
- if (currentUrl.startsWith("https://notebooklm.google.com/")) {
490
- log.success(" ✅ NotebookLM URL detected");
489
+ if (currentUrl.startsWith('https://notebooklm.google.com/')) {
490
+ log.success(' ✅ NotebookLM URL detected');
491
491
  return true;
492
492
  }
493
493
  }
@@ -503,10 +503,10 @@ export class AuthManager {
503
503
  */
504
504
  async handleAccountChooser(page, email) {
505
505
  try {
506
- const chooser = await page.$$("div[data-identifier], li[data-identifier]");
506
+ const chooser = await page.$$('div[data-identifier], li[data-identifier]');
507
507
  if (chooser.length > 0) {
508
508
  for (const item of chooser) {
509
- const identifier = (await item.getAttribute("data-identifier"))?.toLowerCase() || "";
509
+ const identifier = (await item.getAttribute('data-identifier'))?.toLowerCase() || '';
510
510
  if (identifier === email.toLowerCase()) {
511
511
  await item.click();
512
512
  await randomDelay(150, 320);
@@ -516,9 +516,9 @@ export class AuthManager {
516
516
  }
517
517
  // Click "Use another account"
518
518
  await this.clickText(page, [
519
- "Use another account",
520
- "Weiteres Konto hinzufügen",
521
- "Anderes Konto verwenden",
519
+ 'Use another account',
520
+ 'Weiteres Konto hinzufügen',
521
+ 'Anderes Konto verwenden',
522
522
  ]);
523
523
  await randomDelay(150, 320);
524
524
  return false;
@@ -533,9 +533,9 @@ export class AuthManager {
533
533
  * Fill email identifier field with human-like typing
534
534
  */
535
535
  async fillIdentifier(page, email) {
536
- log.info(" 📧 Looking for email field...");
536
+ log.info(' 📧 Looking for email field...');
537
537
  const emailSelectors = [
538
- "input#identifierId",
538
+ 'input#identifierId',
539
539
  "input[name='identifier']",
540
540
  "input[type='email']",
541
541
  ];
@@ -544,7 +544,7 @@ export class AuthManager {
544
544
  for (const selector of emailSelectors) {
545
545
  try {
546
546
  const candidate = await page.waitForSelector(selector, {
547
- state: "attached",
547
+ state: 'attached',
548
548
  timeout: 3000,
549
549
  });
550
550
  if (!candidate)
@@ -567,7 +567,7 @@ export class AuthManager {
567
567
  }
568
568
  }
569
569
  if (!emailField || !emailSelector) {
570
- log.warning(" ℹ️ No visible email field found (likely pre-filled)");
570
+ log.warning(' ℹ️ No visible email field found (likely pre-filled)');
571
571
  log.info(` 📍 Current URL: ${page.url().slice(0, 100)}`);
572
572
  return false;
573
573
  }
@@ -594,22 +594,23 @@ export class AuthManager {
594
594
  await emailField.focus();
595
595
  }
596
596
  catch {
597
- log.error(" ❌ Failed to focus email field");
597
+ log.error(' ❌ Failed to focus email field');
598
598
  return false;
599
599
  }
600
600
  }
601
601
  // ✅ FASTER: Programmer typing speed (90-120 WPM from config)
602
602
  log.info(` ⌨️ Typing email: ${this.maskEmail(email)}`);
603
603
  try {
604
- 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));
605
606
  await humanType(page, emailSelector, email, { wpm, withTypos: false });
606
- log.success(" ✅ Email typed successfully");
607
+ log.success(' ✅ Email typed successfully');
607
608
  }
608
609
  catch (error) {
609
610
  log.error(` ❌ Typing failed: ${error}`);
610
611
  try {
611
612
  await page.fill(emailSelector, email);
612
- log.success(" ✅ Filled email using fallback");
613
+ log.success(' ✅ Filled email using fallback');
613
614
  }
614
615
  catch {
615
616
  return false;
@@ -618,11 +619,11 @@ export class AuthManager {
618
619
  // Human "thinking" pause before clicking Next
619
620
  await randomDelay(400, 1200);
620
621
  // Click Next button
621
- log.info(" 🔘 Looking for Next button...");
622
+ log.info(' 🔘 Looking for Next button...');
622
623
  const nextSelectors = [
623
624
  "button:has-text('Next')",
624
625
  "button:has-text('Weiter')",
625
- "#identifierNext",
626
+ '#identifierNext',
626
627
  ];
627
628
  let nextClicked = false;
628
629
  for (const selector of nextSelectors) {
@@ -640,19 +641,19 @@ export class AuthManager {
640
641
  }
641
642
  }
642
643
  if (!nextClicked) {
643
- log.warning(" ⚠️ Button not found, pressing Enter");
644
- await emailField.press("Enter");
644
+ log.warning(' ⚠️ Button not found, pressing Enter');
645
+ await emailField.press('Enter');
645
646
  }
646
647
  // Variable delay
647
648
  await randomDelay(800, 1500);
648
- log.success(" ✅ Email step complete");
649
+ log.success(' ✅ Email step complete');
649
650
  return true;
650
651
  }
651
652
  /**
652
653
  * Fill password field with human-like typing
653
654
  */
654
655
  async fillPassword(page, password) {
655
- log.info(" 🔐 Looking for password field...");
656
+ log.info(' 🔐 Looking for password field...');
656
657
  const passwordSelectors = ["input[name='Passwd']", "input[type='password']"];
657
658
  let passwordSelector = null;
658
659
  let passwordField = null;
@@ -691,13 +692,14 @@ export class AuthManager {
691
692
  await realisticClick(page, passwordSelector, false);
692
693
  }
693
694
  // ✅ FASTER: Programmer typing speed (90-120 WPM from config)
694
- log.info(" ⌨️ Typing password...");
695
+ log.info(' ⌨️ Typing password...');
695
696
  try {
696
- 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));
697
699
  if (passwordSelector) {
698
700
  await humanType(page, passwordSelector, password, { wpm, withTypos: false });
699
701
  }
700
- log.success(" ✅ Password typed successfully");
702
+ log.success(' ✅ Password typed successfully');
701
703
  }
702
704
  catch (error) {
703
705
  log.error(` ❌ Typing failed: ${error}`);
@@ -706,11 +708,11 @@ export class AuthManager {
706
708
  // Human "review" pause before submitting password
707
709
  await randomDelay(300, 1000);
708
710
  // Click Next button
709
- log.info(" 🔘 Looking for Next button...");
711
+ log.info(' 🔘 Looking for Next button...');
710
712
  const pwdNextSelectors = [
711
713
  "button:has-text('Next')",
712
714
  "button:has-text('Weiter')",
713
- "#passwordNext",
715
+ '#passwordNext',
714
716
  ];
715
717
  let pwdNextClicked = false;
716
718
  for (const selector of pwdNextSelectors) {
@@ -728,12 +730,12 @@ export class AuthManager {
728
730
  }
729
731
  }
730
732
  if (!pwdNextClicked) {
731
- log.warning(" ⚠️ Button not found, pressing Enter");
732
- await passwordField.press("Enter");
733
+ log.warning(' ⚠️ Button not found, pressing Enter');
734
+ await passwordField.press('Enter');
733
735
  }
734
736
  // Variable delay
735
737
  await randomDelay(800, 1500);
736
- log.success(" ✅ Password step complete");
738
+ log.success(' ✅ Password step complete');
737
739
  return true;
738
740
  }
739
741
  /**
@@ -760,14 +762,14 @@ export class AuthManager {
760
762
  * Mask email for logging
761
763
  */
762
764
  maskEmail(email) {
763
- if (!email.includes("@")) {
764
- return "***";
765
+ if (!email.includes('@')) {
766
+ return '***';
765
767
  }
766
- const [name, domain] = email.split("@");
768
+ const [name, domain] = email.split('@');
767
769
  if (name.length <= 2) {
768
- return `${"*".repeat(name.length)}@${domain}`;
770
+ return `${'*'.repeat(name.length)}@${domain}`;
769
771
  }
770
- 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}`;
771
773
  }
772
774
  // ============================================================================
773
775
  // Additional Helper Methods
@@ -778,7 +780,7 @@ export class AuthManager {
778
780
  async loadAuthState(context, statePath) {
779
781
  try {
780
782
  // Read state.json
781
- const stateData = await fs.readFile(statePath, { encoding: "utf-8" });
783
+ const stateData = await fs.readFile(statePath, { encoding: 'utf-8' });
782
784
  const state = JSON.parse(stateData);
783
785
  // Add cookies to context
784
786
  if (state.cookies) {
@@ -812,7 +814,7 @@ export class AuthManager {
812
814
  * If not provided, defaults to true (visible) for setup
813
815
  */
814
816
  async performSetup(sendProgress, overrideHeadless) {
815
- const { chromium } = await import("patchright");
817
+ const { chromium } = await import('patchright');
816
818
  // Determine headless mode: override or default to true (visible for setup)
817
819
  // overrideHeadless contains show_browser value (true = show, false = hide)
818
820
  const shouldShowBrowser = overrideHeadless !== undefined ? overrideHeadless : true;
@@ -821,29 +823,29 @@ export class AuthManager {
821
823
  const statePath = await this.getValidStatePath();
822
824
  const isAuthenticated = statePath !== null;
823
825
  if (isAuthenticated) {
824
- log.info("✅ Already authenticated, skipping setup");
826
+ log.info('✅ Already authenticated, skipping setup');
825
827
  log.info(" Use 're_auth' tool to switch accounts or re-authenticate");
826
- await sendProgress?.("Already authenticated!", 10, 10);
828
+ await sendProgress?.('Already authenticated!', 10, 10);
827
829
  return true;
828
830
  }
829
- log.info("🔄 Preparing for first-time authentication...");
830
- await sendProgress?.("Preparing browser...", 1, 10);
831
- 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...');
832
834
  log.info(` 📍 Profile: ${CONFIG.chromeProfileDir}`);
833
- await sendProgress?.("Launching persistent browser...", 2, 10);
835
+ await sendProgress?.('Launching persistent browser...', 2, 10);
834
836
  // ✅ CRITICAL FIX: Use launchPersistentContext (same as runtime!)
835
837
  // This ensures session cookies persist correctly
836
838
  const context = await chromium.launchPersistentContext(CONFIG.chromeProfileDir, {
837
839
  headless: !shouldShowBrowser, // Use override or default to visible for setup
838
- channel: "chrome",
840
+ channel: 'chrome',
839
841
  viewport: CONFIG.viewport,
840
- locale: "en-US",
841
- timezoneId: "Europe/Berlin",
842
+ locale: 'en-US',
843
+ timezoneId: 'Europe/Berlin',
842
844
  args: [
843
- "--disable-blink-features=AutomationControlled",
844
- "--disable-dev-shm-usage",
845
- "--no-first-run",
846
- "--no-default-browser-check",
845
+ '--disable-blink-features=AutomationControlled',
846
+ '--disable-dev-shm-usage',
847
+ '--no-first-run',
848
+ '--no-default-browser-check',
847
849
  ],
848
850
  });
849
851
  // Get or create a page
@@ -854,17 +856,17 @@ export class AuthManager {
854
856
  if (loginSuccess) {
855
857
  // ✅ Save browser state to state.json (for validation & backup)
856
858
  // Chrome ALSO saves everything to the persistent profile automatically!
857
- await sendProgress?.("Saving authentication state...", 9, 10);
859
+ await sendProgress?.('Saving authentication state...', 9, 10);
858
860
  await this.saveBrowserState(context, page);
859
861
  // ✅ CRITICAL FIX: Wait for Chrome to flush profile to disk
860
862
  // Windows needs time to write persistent data (cookies, cache, session storage, etc.)
861
863
  // Without this delay, chrome_profile/ folder remains empty!
862
- log.info("⏳ Waiting for Chrome to finalize profile writes (5 seconds)...");
864
+ log.info('⏳ Waiting for Chrome to finalize profile writes (5 seconds)...');
863
865
  await page.waitForTimeout(5000); // 5 seconds buffer for Windows filesystem
864
- log.success("✅ Setup complete - authentication saved to:");
866
+ log.success('✅ Setup complete - authentication saved to:');
865
867
  log.success(` 📄 State file: ${this.stateFilePath}`);
866
868
  log.success(` 📁 Chrome profile: ${CONFIG.chromeProfileDir}`);
867
- log.info("💡 Session cookies will now persist across restarts!");
869
+ log.info('💡 Session cookies will now persist across restarts!');
868
870
  }
869
871
  // Close persistent context
870
872
  await context.close();
@@ -889,13 +891,13 @@ export class AuthManager {
889
891
  * Use this BEFORE authenticating a new account!
890
892
  */
891
893
  async clearAllAuthData() {
892
- log.warning("🗑️ Clearing ALL authentication data for account switch...");
894
+ log.warning('🗑️ Clearing ALL authentication data for account switch...');
893
895
  let deletedCount = 0;
894
896
  // 1. Delete all state files in browser_state_dir
895
897
  try {
896
898
  const files = await fs.readdir(CONFIG.browserStateDir);
897
899
  for (const file of files) {
898
- if (file.endsWith(".json")) {
900
+ if (file.endsWith('.json')) {
899
901
  await fs.unlink(path.join(CONFIG.browserStateDir, file));
900
902
  log.info(` ✅ Deleted: ${file}`);
901
903
  deletedCount++;
@@ -919,7 +921,7 @@ export class AuthManager {
919
921
  log.warning(` ⚠️ Could not delete Chrome profile: ${error}`);
920
922
  }
921
923
  if (deletedCount === 0) {
922
- log.info(" ℹ️ No old auth data found (already clean)");
924
+ log.info(' ℹ️ No old auth data found (already clean)');
923
925
  }
924
926
  else {
925
927
  log.success(`✅ All auth data cleared (${deletedCount} items) - ready for new account!`);
@@ -942,7 +944,7 @@ export class AuthManager {
942
944
  catch {
943
945
  // File doesn't exist
944
946
  }
945
- log.success("✅ Authentication state cleared");
947
+ log.success('✅ Authentication state cleared');
946
948
  return true;
947
949
  }
948
950
  catch (error) {
@@ -955,7 +957,7 @@ export class AuthManager {
955
957
  */
956
958
  async hardResetState() {
957
959
  try {
958
- log.warning("🧹 Performing HARD RESET of all authentication state...");
960
+ log.warning('🧹 Performing HARD RESET of all authentication state...');
959
961
  let deletedCount = 0;
960
962
  // Delete state file
961
963
  try {
@@ -988,7 +990,7 @@ export class AuthManager {
988
990
  // Directory doesn't exist or empty
989
991
  }
990
992
  if (deletedCount === 0) {
991
- log.info(" ℹ️ No state to delete (already clean)");
993
+ log.info(' ℹ️ No state to delete (already clean)');
992
994
  }
993
995
  else {
994
996
  log.success(`✅ Hard reset complete: ${deletedCount} items deleted`);