@pan-sec/notebooklm-mcp 2026.2.11 → 2026.3.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 (305) hide show
  1. package/README.md +62 -19
  2. package/SECURITY.md +31 -61
  3. package/dist/auth/auth-manager.d.ts +2 -1
  4. package/dist/auth/auth-manager.d.ts.map +1 -1
  5. package/dist/auth/auth-manager.js +117 -44
  6. package/dist/auth/auth-manager.js.map +1 -1
  7. package/dist/auth/mcp-auth.d.ts +24 -4
  8. package/dist/auth/mcp-auth.d.ts.map +1 -1
  9. package/dist/auth/mcp-auth.js +149 -19
  10. package/dist/auth/mcp-auth.js.map +1 -1
  11. package/dist/compliance/alert-manager.d.ts.map +1 -1
  12. package/dist/compliance/alert-manager.js +7 -4
  13. package/dist/compliance/alert-manager.js.map +1 -1
  14. package/dist/compliance/breach-detection.d.ts.map +1 -1
  15. package/dist/compliance/breach-detection.js +14 -7
  16. package/dist/compliance/breach-detection.js.map +1 -1
  17. package/dist/compliance/change-log.d.ts.map +1 -1
  18. package/dist/compliance/change-log.js +7 -4
  19. package/dist/compliance/change-log.js.map +1 -1
  20. package/dist/compliance/compliance-logger.d.ts.map +1 -1
  21. package/dist/compliance/compliance-logger.js +11 -6
  22. package/dist/compliance/compliance-logger.js.map +1 -1
  23. package/dist/compliance/consent-manager.d.ts.map +1 -1
  24. package/dist/compliance/consent-manager.js +5 -3
  25. package/dist/compliance/consent-manager.js.map +1 -1
  26. package/dist/compliance/data-erasure.d.ts +1 -1
  27. package/dist/compliance/data-erasure.d.ts.map +1 -1
  28. package/dist/compliance/data-erasure.js +142 -83
  29. package/dist/compliance/data-erasure.js.map +1 -1
  30. package/dist/compliance/data-export.d.ts.map +1 -1
  31. package/dist/compliance/data-export.js +23 -12
  32. package/dist/compliance/data-export.js.map +1 -1
  33. package/dist/compliance/data-inventory.d.ts.map +1 -1
  34. package/dist/compliance/data-inventory.js +7 -6
  35. package/dist/compliance/data-inventory.js.map +1 -1
  36. package/dist/compliance/dsar-handler.d.ts +7 -1
  37. package/dist/compliance/dsar-handler.d.ts.map +1 -1
  38. package/dist/compliance/dsar-handler.js +74 -61
  39. package/dist/compliance/dsar-handler.js.map +1 -1
  40. package/dist/compliance/evidence-collector.d.ts.map +1 -1
  41. package/dist/compliance/evidence-collector.js +10 -6
  42. package/dist/compliance/evidence-collector.js.map +1 -1
  43. package/dist/compliance/health-monitor.d.ts.map +1 -1
  44. package/dist/compliance/health-monitor.js +15 -9
  45. package/dist/compliance/health-monitor.js.map +1 -1
  46. package/dist/compliance/incident-manager.d.ts.map +1 -1
  47. package/dist/compliance/incident-manager.js +5 -3
  48. package/dist/compliance/incident-manager.js.map +1 -1
  49. package/dist/compliance/policy-docs.d.ts.map +1 -1
  50. package/dist/compliance/policy-docs.js +14 -11
  51. package/dist/compliance/policy-docs.js.map +1 -1
  52. package/dist/compliance/privacy-notice-text.d.ts.map +1 -1
  53. package/dist/compliance/privacy-notice-text.js +3 -4
  54. package/dist/compliance/privacy-notice-text.js.map +1 -1
  55. package/dist/compliance/privacy-notice.d.ts.map +1 -1
  56. package/dist/compliance/privacy-notice.js +5 -3
  57. package/dist/compliance/privacy-notice.js.map +1 -1
  58. package/dist/compliance/report-generator.d.ts.map +1 -1
  59. package/dist/compliance/report-generator.js +5 -3
  60. package/dist/compliance/report-generator.js.map +1 -1
  61. package/dist/compliance/retention-engine.d.ts.map +1 -1
  62. package/dist/compliance/retention-engine.js +24 -10
  63. package/dist/compliance/retention-engine.js.map +1 -1
  64. package/dist/compliance/siem-exporter.d.ts.map +1 -1
  65. package/dist/compliance/siem-exporter.js +40 -16
  66. package/dist/compliance/siem-exporter.js.map +1 -1
  67. package/dist/config.d.ts +8 -31
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +26 -64
  70. package/dist/config.js.map +1 -1
  71. package/dist/errors.d.ts +22 -2
  72. package/dist/errors.d.ts.map +1 -1
  73. package/dist/errors.js +55 -4
  74. package/dist/errors.js.map +1 -1
  75. package/dist/gemini/gemini-client.d.ts +1 -0
  76. package/dist/gemini/gemini-client.d.ts.map +1 -1
  77. package/dist/gemini/gemini-client.js +50 -49
  78. package/dist/gemini/gemini-client.js.map +1 -1
  79. package/dist/gemini/types.d.ts +3 -1
  80. package/dist/gemini/types.d.ts.map +1 -1
  81. package/dist/gemini/types.js.map +1 -1
  82. package/dist/index.d.ts +52 -1
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +412 -89
  85. package/dist/index.js.map +1 -1
  86. package/dist/library/notebook-library.d.ts.map +1 -1
  87. package/dist/library/notebook-library.js +2 -1
  88. package/dist/library/notebook-library.js.map +1 -1
  89. package/dist/logging/query-logger.d.ts +13 -1
  90. package/dist/logging/query-logger.d.ts.map +1 -1
  91. package/dist/logging/query-logger.js +62 -10
  92. package/dist/logging/query-logger.js.map +1 -1
  93. package/dist/notebook-creation/audio-manager.d.ts.map +1 -1
  94. package/dist/notebook-creation/audio-manager.js +19 -24
  95. package/dist/notebook-creation/audio-manager.js.map +1 -1
  96. package/dist/notebook-creation/browser-options.d.ts +28 -0
  97. package/dist/notebook-creation/browser-options.d.ts.map +1 -0
  98. package/dist/notebook-creation/browser-options.js +75 -0
  99. package/dist/notebook-creation/browser-options.js.map +1 -0
  100. package/dist/notebook-creation/data-table-manager.d.ts.map +1 -1
  101. package/dist/notebook-creation/data-table-manager.js +20 -21
  102. package/dist/notebook-creation/data-table-manager.js.map +1 -1
  103. package/dist/notebook-creation/discover-creation-flow.d.ts +0 -6
  104. package/dist/notebook-creation/discover-creation-flow.d.ts.map +1 -1
  105. package/dist/notebook-creation/discover-creation-flow.js +10 -10
  106. package/dist/notebook-creation/discover-creation-flow.js.map +1 -1
  107. package/dist/notebook-creation/discover-quota.d.ts +0 -6
  108. package/dist/notebook-creation/discover-quota.d.ts.map +1 -1
  109. package/dist/notebook-creation/discover-quota.js +12 -13
  110. package/dist/notebook-creation/discover-quota.js.map +1 -1
  111. package/dist/notebook-creation/discover-sources.js +15 -16
  112. package/dist/notebook-creation/discover-sources.js.map +1 -1
  113. package/dist/notebook-creation/dom-scripts.d.ts +10 -0
  114. package/dist/notebook-creation/dom-scripts.d.ts.map +1 -0
  115. package/dist/notebook-creation/dom-scripts.js +58 -0
  116. package/dist/notebook-creation/dom-scripts.js.map +1 -0
  117. package/dist/notebook-creation/errors.d.ts +18 -0
  118. package/dist/notebook-creation/errors.d.ts.map +1 -0
  119. package/dist/notebook-creation/errors.js +20 -0
  120. package/dist/notebook-creation/errors.js.map +1 -0
  121. package/dist/notebook-creation/index.d.ts +2 -1
  122. package/dist/notebook-creation/index.d.ts.map +1 -1
  123. package/dist/notebook-creation/index.js +2 -1
  124. package/dist/notebook-creation/index.js.map +1 -1
  125. package/dist/notebook-creation/notebook-creator.d.ts +6 -82
  126. package/dist/notebook-creation/notebook-creator.d.ts.map +1 -1
  127. package/dist/notebook-creation/notebook-creator.js +49 -835
  128. package/dist/notebook-creation/notebook-creator.js.map +1 -1
  129. package/dist/notebook-creation/notebook-nav.d.ts +19 -0
  130. package/dist/notebook-creation/notebook-nav.d.ts.map +1 -0
  131. package/dist/notebook-creation/notebook-nav.js +240 -0
  132. package/dist/notebook-creation/notebook-nav.js.map +1 -0
  133. package/dist/notebook-creation/notebook-sync.d.ts.map +1 -1
  134. package/dist/notebook-creation/notebook-sync.js +36 -38
  135. package/dist/notebook-creation/notebook-sync.js.map +1 -1
  136. package/dist/notebook-creation/selector-discovery.d.ts.map +1 -1
  137. package/dist/notebook-creation/selector-discovery.js +17 -24
  138. package/dist/notebook-creation/selector-discovery.js.map +1 -1
  139. package/dist/notebook-creation/selectors.d.ts +23 -37
  140. package/dist/notebook-creation/selectors.d.ts.map +1 -1
  141. package/dist/notebook-creation/selectors.js +56 -60
  142. package/dist/notebook-creation/selectors.js.map +1 -1
  143. package/dist/notebook-creation/source-manager.d.ts +25 -0
  144. package/dist/notebook-creation/source-manager.d.ts.map +1 -1
  145. package/dist/notebook-creation/source-manager.js +689 -50
  146. package/dist/notebook-creation/source-manager.js.map +1 -1
  147. package/dist/notebook-creation/types.d.ts +4 -0
  148. package/dist/notebook-creation/types.d.ts.map +1 -1
  149. package/dist/notebook-creation/video-manager.d.ts.map +1 -1
  150. package/dist/notebook-creation/video-manager.js +33 -35
  151. package/dist/notebook-creation/video-manager.js.map +1 -1
  152. package/dist/observability/metrics.d.ts +19 -0
  153. package/dist/observability/metrics.d.ts.map +1 -0
  154. package/dist/observability/metrics.js +35 -0
  155. package/dist/observability/metrics.js.map +1 -0
  156. package/dist/quota/quota-manager.d.ts +11 -3
  157. package/dist/quota/quota-manager.d.ts.map +1 -1
  158. package/dist/quota/quota-manager.js +139 -47
  159. package/dist/quota/quota-manager.js.map +1 -1
  160. package/dist/resources/resource-handlers.d.ts.map +1 -1
  161. package/dist/resources/resource-handlers.js +39 -17
  162. package/dist/resources/resource-handlers.js.map +1 -1
  163. package/dist/session/browser-session.d.ts.map +1 -1
  164. package/dist/session/browser-session.js +22 -22
  165. package/dist/session/browser-session.js.map +1 -1
  166. package/dist/session/session-timeout.d.ts.map +1 -1
  167. package/dist/session/session-timeout.js +4 -2
  168. package/dist/session/session-timeout.js.map +1 -1
  169. package/dist/session/shared-context-manager.d.ts.map +1 -1
  170. package/dist/session/shared-context-manager.js +31 -30
  171. package/dist/session/shared-context-manager.js.map +1 -1
  172. package/dist/tools/annotations.d.ts.map +1 -1
  173. package/dist/tools/annotations.js +9 -56
  174. package/dist/tools/annotations.js.map +1 -1
  175. package/dist/tools/definitions/ask-question.d.ts.map +1 -1
  176. package/dist/tools/definitions/ask-question.js +35 -100
  177. package/dist/tools/definitions/ask-question.js.map +1 -1
  178. package/dist/tools/definitions/chat-history.d.ts +47 -1
  179. package/dist/tools/definitions/chat-history.d.ts.map +1 -1
  180. package/dist/tools/definitions/chat-history.js +10 -1
  181. package/dist/tools/definitions/chat-history.js.map +1 -1
  182. package/dist/tools/definitions/data-tables.d.ts.map +1 -1
  183. package/dist/tools/definitions/data-tables.js +2 -0
  184. package/dist/tools/definitions/data-tables.js.map +1 -1
  185. package/dist/tools/definitions/gemini.d.ts.map +1 -1
  186. package/dist/tools/definitions/gemini.js +54 -11
  187. package/dist/tools/definitions/gemini.js.map +1 -1
  188. package/dist/tools/definitions/notebook-management.d.ts.map +1 -1
  189. package/dist/tools/definitions/notebook-management.js +100 -70
  190. package/dist/tools/definitions/notebook-management.js.map +1 -1
  191. package/dist/tools/definitions/query-history.d.ts +47 -1
  192. package/dist/tools/definitions/query-history.d.ts.map +1 -1
  193. package/dist/tools/definitions/query-history.js +7 -0
  194. package/dist/tools/definitions/query-history.js.map +1 -1
  195. package/dist/tools/definitions/session-management.d.ts.map +1 -1
  196. package/dist/tools/definitions/session-management.js +5 -0
  197. package/dist/tools/definitions/session-management.js.map +1 -1
  198. package/dist/tools/definitions/system.d.ts.map +1 -1
  199. package/dist/tools/definitions/system.js +71 -100
  200. package/dist/tools/definitions/system.js.map +1 -1
  201. package/dist/tools/definitions/video.d.ts.map +1 -1
  202. package/dist/tools/definitions/video.js +4 -1
  203. package/dist/tools/definitions/video.js.map +1 -1
  204. package/dist/tools/definitions.d.ts.map +1 -1
  205. package/dist/tools/definitions.js +4 -0
  206. package/dist/tools/definitions.js.map +1 -1
  207. package/dist/tools/handlers/ask-question.d.ts +1 -1
  208. package/dist/tools/handlers/ask-question.d.ts.map +1 -1
  209. package/dist/tools/handlers/ask-question.js +57 -13
  210. package/dist/tools/handlers/ask-question.js.map +1 -1
  211. package/dist/tools/handlers/audio-video.d.ts.map +1 -1
  212. package/dist/tools/handlers/audio-video.js +22 -161
  213. package/dist/tools/handlers/audio-video.js.map +1 -1
  214. package/dist/tools/handlers/auth.d.ts +14 -19
  215. package/dist/tools/handlers/auth.d.ts.map +1 -1
  216. package/dist/tools/handlers/auth.js +77 -121
  217. package/dist/tools/handlers/auth.js.map +1 -1
  218. package/dist/tools/handlers/error-utils.d.ts +16 -0
  219. package/dist/tools/handlers/error-utils.d.ts.map +1 -0
  220. package/dist/tools/handlers/error-utils.js +39 -0
  221. package/dist/tools/handlers/error-utils.js.map +1 -0
  222. package/dist/tools/handlers/gemini.d.ts +2 -0
  223. package/dist/tools/handlers/gemini.d.ts.map +1 -1
  224. package/dist/tools/handlers/gemini.js +88 -51
  225. package/dist/tools/handlers/gemini.js.map +1 -1
  226. package/dist/tools/handlers/index.d.ts +39 -47
  227. package/dist/tools/handlers/index.d.ts.map +1 -1
  228. package/dist/tools/handlers/index.js +15 -4
  229. package/dist/tools/handlers/index.js.map +1 -1
  230. package/dist/tools/handlers/notebook-creation.d.ts.map +1 -1
  231. package/dist/tools/handlers/notebook-creation.js +102 -86
  232. package/dist/tools/handlers/notebook-creation.js.map +1 -1
  233. package/dist/tools/handlers/notebook-management.d.ts +8 -8
  234. package/dist/tools/handlers/notebook-management.d.ts.map +1 -1
  235. package/dist/tools/handlers/notebook-management.js +34 -80
  236. package/dist/tools/handlers/notebook-management.js.map +1 -1
  237. package/dist/tools/handlers/session-management.d.ts +8 -10
  238. package/dist/tools/handlers/session-management.d.ts.map +1 -1
  239. package/dist/tools/handlers/session-management.js +34 -63
  240. package/dist/tools/handlers/session-management.js.map +1 -1
  241. package/dist/tools/handlers/system.d.ts.map +1 -1
  242. package/dist/tools/handlers/system.js +45 -10
  243. package/dist/tools/handlers/system.js.map +1 -1
  244. package/dist/tools/handlers/types.d.ts +1 -1
  245. package/dist/tools/handlers/types.d.ts.map +1 -1
  246. package/dist/tools/handlers/webhooks.d.ts.map +1 -1
  247. package/dist/tools/handlers/webhooks.js +15 -13
  248. package/dist/tools/handlers/webhooks.js.map +1 -1
  249. package/dist/types.d.ts +7 -17
  250. package/dist/types.d.ts.map +1 -1
  251. package/dist/utils/audit-logger.d.ts +19 -1
  252. package/dist/utils/audit-logger.d.ts.map +1 -1
  253. package/dist/utils/audit-logger.js +198 -30
  254. package/dist/utils/audit-logger.js.map +1 -1
  255. package/dist/utils/cleanup-manager.d.ts.map +1 -1
  256. package/dist/utils/cleanup-manager.js +6 -3
  257. package/dist/utils/cleanup-manager.js.map +1 -1
  258. package/dist/utils/crypto.d.ts +4 -1
  259. package/dist/utils/crypto.d.ts.map +1 -1
  260. package/dist/utils/crypto.js +32 -21
  261. package/dist/utils/crypto.js.map +1 -1
  262. package/dist/utils/file-lock.d.ts.map +1 -1
  263. package/dist/utils/file-lock.js +87 -16
  264. package/dist/utils/file-lock.js.map +1 -1
  265. package/dist/utils/file-permissions.d.ts +2 -0
  266. package/dist/utils/file-permissions.d.ts.map +1 -1
  267. package/dist/utils/file-permissions.js +2 -1
  268. package/dist/utils/file-permissions.js.map +1 -1
  269. package/dist/utils/logger.d.ts +4 -0
  270. package/dist/utils/logger.d.ts.map +1 -1
  271. package/dist/utils/logger.js +16 -0
  272. package/dist/utils/logger.js.map +1 -1
  273. package/dist/utils/page-utils.d.ts +13 -0
  274. package/dist/utils/page-utils.d.ts.map +1 -1
  275. package/dist/utils/page-utils.js +61 -39
  276. package/dist/utils/page-utils.js.map +1 -1
  277. package/dist/utils/response-validator.d.ts.map +1 -1
  278. package/dist/utils/response-validator.js +27 -22
  279. package/dist/utils/response-validator.js.map +1 -1
  280. package/dist/utils/secrets-scanner.d.ts +11 -0
  281. package/dist/utils/secrets-scanner.d.ts.map +1 -1
  282. package/dist/utils/secrets-scanner.js +65 -17
  283. package/dist/utils/secrets-scanner.js.map +1 -1
  284. package/dist/utils/secure-memory.d.ts +9 -31
  285. package/dist/utils/secure-memory.d.ts.map +1 -1
  286. package/dist/utils/secure-memory.js +17 -102
  287. package/dist/utils/secure-memory.js.map +1 -1
  288. package/dist/utils/security.d.ts +4 -3
  289. package/dist/utils/security.d.ts.map +1 -1
  290. package/dist/utils/security.js +43 -13
  291. package/dist/utils/security.js.map +1 -1
  292. package/dist/utils/stealth-utils.d.ts.map +1 -1
  293. package/dist/utils/stealth-utils.js +4 -4
  294. package/dist/utils/stealth-utils.js.map +1 -1
  295. package/dist/webhooks/types.d.ts +4 -0
  296. package/dist/webhooks/types.d.ts.map +1 -1
  297. package/dist/webhooks/webhook-dispatcher.d.ts +80 -12
  298. package/dist/webhooks/webhook-dispatcher.d.ts.map +1 -1
  299. package/dist/webhooks/webhook-dispatcher.js +497 -74
  300. package/dist/webhooks/webhook-dispatcher.js.map +1 -1
  301. package/docs/archive/ISSUES-legacy-2026-04-24.md +644 -0
  302. package/docs/dependency-risk.md +25 -0
  303. package/docs/testing-runbook.md +166 -0
  304. package/docs/usage-guide.md +2 -1
  305. package/package.json +34 -16
@@ -7,7 +7,10 @@ import * as fs from "fs";
7
7
  import * as path from "path";
8
8
  import { log } from "../utils/logger.js";
9
9
  import { randomDelay, humanType } from "../utils/stealth-utils.js";
10
- import { NOTEBOOKLM_SELECTORS } from "./selectors.js";
10
+ import { getSelectors } from "./selectors.js";
11
+ import { findElement } from "../utils/page-utils.js";
12
+ const STANDARD_SOURCE_PROCESSING_TIMEOUT_MS = 30000;
13
+ const LENIENT_SOURCE_PROCESSING_TIMEOUT_MS = 60000;
11
14
  export class SourceManager {
12
15
  authManager;
13
16
  contextManager;
@@ -42,6 +45,7 @@ export class SourceManager {
42
45
  await page.waitForTimeout(2000);
43
46
  // Extract source information from the page
44
47
  const sources = await page.evaluate(() => {
48
+ const browser = globalThis;
45
49
  const results = [];
46
50
  // Look for source items in the sidebar/sources panel
47
51
  // Common patterns in NotebookLM:
@@ -56,8 +60,7 @@ export class SourceManager {
56
60
  '.sources-list > *',
57
61
  ];
58
62
  for (const selector of sourceSelectors) {
59
- // @ts-expect-error - DOM types
60
- const items = document.querySelectorAll(selector);
63
+ const items = Array.from(browser.document.querySelectorAll(selector));
61
64
  for (let i = 0; i < items.length; i++) {
62
65
  const item = items[i];
63
66
  const text = item.textContent?.trim() || "";
@@ -125,27 +128,50 @@ export class SourceManager {
125
128
  try {
126
129
  await page.waitForLoadState('networkidle', { timeout: 15000 });
127
130
  }
128
- catch {
131
+ catch (err) {
132
+ log.debug(`source-manager: waiting for network idle after page load: ${err instanceof Error ? err.message : String(err)}`);
129
133
  log.warning(" Network idle timeout, continuing...");
130
134
  }
131
135
  await randomDelay(3000, 4000);
132
136
  // Check page state
133
137
  const pageState = await page.evaluate(() => {
134
- // @ts-expect-error - DOM types in evaluate
135
- const addSourceBtn = document.querySelector('button[aria-label="Add source"]');
136
- // @ts-expect-error - window in evaluate
137
- return { hasAddSourceBtn: !!addSourceBtn, windowWidth: window.innerWidth };
138
+ const browser = globalThis;
139
+ const addSourceBtn = browser.document.querySelector('button[aria-label="Add source"]');
140
+ return { hasAddSourceBtn: !!addSourceBtn, windowWidth: browser.window.innerWidth };
138
141
  });
139
142
  log.dim(` Page state: width=${pageState.windowWidth}, addSourceBtn=${pageState.hasAddSourceBtn}`);
140
143
  // Check if source dialog is already open (new/empty notebooks may auto-open it)
141
144
  const dialogAlreadyOpen = await page.evaluate(() => {
142
- // Check for dropzone (file upload area) - most reliable indicator
143
- // @ts-expect-error - DOM types
144
- const dropzone = document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
145
- // Check for source type options in dialog
146
- // @ts-expect-error - DOM types
147
- const sourceOptions = document.querySelector('[aria-label="Upload sources from your computer"]');
148
- return !!(dropzone || sourceOptions);
145
+ const browser = globalThis;
146
+ const asElements = (selector) => Array.from(browser.document.querySelectorAll(selector));
147
+ const dropzone = browser.document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
148
+ if (dropzone && dropzone.offsetParent !== null)
149
+ return true;
150
+ const sourceOptions = browser.document.querySelector('[aria-label="Upload sources from your computer"]');
151
+ if (sourceOptions && sourceOptions.offsetParent !== null)
152
+ return true;
153
+ const textArea = browser.document.querySelector('textarea.text-area, textarea[class*="text-area"], textarea.mat-mdc-form-field-textarea-control');
154
+ if (textArea && textArea.offsetParent !== null)
155
+ return true;
156
+ const chips = asElements("mat-chip, mat-chip-option, [mat-chip-option], button, span");
157
+ for (const chip of chips) {
158
+ if (chip.offsetParent === null)
159
+ continue;
160
+ const text = chip.textContent?.trim() || "";
161
+ if (text.includes("Copied text") || text.includes("Website") || text.includes("Upload")) {
162
+ return true;
163
+ }
164
+ }
165
+ const dialogs = asElements("mat-dialog-container, [role=\"dialog\"], .cdk-overlay-pane");
166
+ for (const dialog of dialogs) {
167
+ if (dialog.offsetParent === null)
168
+ continue;
169
+ const text = dialog.textContent?.trim() || "";
170
+ if (text.includes("Copied text") || text.includes("Website") || text.includes("Upload")) {
171
+ return true;
172
+ }
173
+ }
174
+ return false;
149
175
  });
150
176
  if (dialogAlreadyOpen) {
151
177
  log.info(" 📋 Source dialog already open");
@@ -184,10 +210,9 @@ export class SourceManager {
184
210
  await randomDelay(1500, 2000);
185
211
  // Verify dialog opened
186
212
  const dialogOpened = await page.evaluate(() => {
187
- // @ts-expect-error - DOM types
188
- const dropzone = document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
189
- // @ts-expect-error - DOM types
190
- const sourceOptions = document.querySelector('[aria-label="Upload sources from your computer"]');
213
+ const browser = globalThis;
214
+ const dropzone = browser.document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
215
+ const sourceOptions = browser.document.querySelector('[aria-label="Upload sources from your computer"]');
191
216
  return !!(dropzone || sourceOptions);
192
217
  });
193
218
  if (!dialogOpened) {
@@ -248,14 +273,14 @@ export class SourceManager {
248
273
  const sourceIndex = parseInt(indexMatch[1], 10);
249
274
  // Find and click on the source to select it
250
275
  const clicked = await page.evaluate((index) => {
276
+ const browser = globalThis;
251
277
  const sourceSelectors = [
252
278
  'mat-list-item',
253
279
  '[class*="source-item"]',
254
280
  '[role="listitem"]',
255
281
  ];
256
282
  for (const selector of sourceSelectors) {
257
- // @ts-expect-error - DOM types
258
- const items = document.querySelectorAll(selector);
283
+ const items = Array.from(browser.document.querySelectorAll(selector));
259
284
  if (items.length > index) {
260
285
  // Look for delete button within the item or select it
261
286
  const item = items[index];
@@ -279,6 +304,7 @@ export class SourceManager {
279
304
  // Look for delete button in toolbar or context menu
280
305
  await randomDelay(500, 800);
281
306
  const deleted = await page.evaluate(() => {
307
+ const browser = globalThis;
282
308
  // Look for delete button that appeared after selection
283
309
  const deleteSelectors = [
284
310
  'button[aria-label*="delete" i]',
@@ -287,8 +313,7 @@ export class SourceManager {
287
313
  '[class*="trash"]',
288
314
  ];
289
315
  for (const selector of deleteSelectors) {
290
- // @ts-expect-error - DOM types
291
- const btn = document.querySelector(selector);
316
+ const btn = browser.document.querySelector(selector);
292
317
  if (btn && btn.offsetParent !== null) {
293
318
  btn.click();
294
319
  return true;
@@ -303,9 +328,9 @@ export class SourceManager {
303
328
  // Confirm deletion if dialog appears
304
329
  await randomDelay(500, 800);
305
330
  await page.evaluate(() => {
331
+ const browser = globalThis;
306
332
  // Look for confirm button in dialog
307
- // @ts-expect-error - DOM types
308
- const buttons = document.querySelectorAll("button");
333
+ const buttons = Array.from(browser.document.querySelectorAll("button"));
309
334
  for (const btn of buttons) {
310
335
  const text = btn.textContent?.toLowerCase() || "";
311
336
  if (text.includes("delete") || text.includes("remove") || text.includes("confirm")) {
@@ -340,16 +365,15 @@ export class SourceManager {
340
365
  async addUrlSourceInternal(page, url) {
341
366
  // Click URL/Website source type option — prefer locale-independent signals
342
367
  const urlOptionClicked = await page.evaluate(() => {
368
+ const browser = globalThis;
343
369
  // Primary: data/value attribute (locale-independent)
344
- // @ts-expect-error - DOM types
345
- const byData = document.querySelector('[data-source-type="url"], [value="url"], mat-chip[value="url"]');
370
+ const byData = browser.document.querySelector('[data-source-type="url"], [value="url"], mat-chip[value="url"]');
346
371
  if (byData) {
347
372
  byData.click();
348
373
  return true;
349
374
  }
350
375
  // Fallback: text/aria match ("URL" is the same word in most languages)
351
- // @ts-expect-error - DOM types
352
- const buttons = document.querySelectorAll("button, [role='button'], mat-chip");
376
+ const buttons = Array.from(browser.document.querySelectorAll("button, [role='button'], mat-chip"));
353
377
  for (const btn of buttons) {
354
378
  const text = btn.textContent?.toLowerCase() || "";
355
379
  const aria = btn.getAttribute("aria-label")?.toLowerCase() || "";
@@ -381,13 +405,17 @@ export class SourceManager {
381
405
  async addTextSourceInternal(page, text, _title) {
382
406
  // Click text/paste option
383
407
  const textOptionClicked = await page.evaluate(() => {
384
- // @ts-expect-error - DOM types
385
- const buttons = document.querySelectorAll("button, [role='button'], mat-chip");
408
+ const browser = globalThis;
409
+ const buttons = Array.from(browser.document.querySelectorAll("button, [role='button'], mat-chip, mat-chip-option, [mat-chip-option]"));
386
410
  for (const btn of buttons) {
387
411
  const btnText = btn.textContent?.toLowerCase() || "";
388
412
  const aria = btn.getAttribute("aria-label")?.toLowerCase() || "";
389
- if (btnText.includes("copied text") || btnText.includes("paste") || btnText.includes("text") ||
390
- aria.includes("copied") || aria.includes("paste")) {
413
+ const combined = `${btnText} ${aria}`;
414
+ if (combined.includes("search the web") || combined.includes("discover sources")) {
415
+ continue;
416
+ }
417
+ if (btnText.includes("copied text") || btnText.includes("paste as text") ||
418
+ aria.includes("copied") || aria.includes("paste as text")) {
391
419
  btn.click();
392
420
  return true;
393
421
  }
@@ -398,16 +426,19 @@ export class SourceManager {
398
426
  throw new Error("Could not find text/paste source option");
399
427
  }
400
428
  await randomDelay(800, 1200);
401
- // Fill text area
402
- const textArea = await page.$(NOTEBOOKLM_SELECTORS.textInput.primary);
429
+ const activeSelector = await this.findValidTextInputSelectorOnPage(page);
430
+ if (!activeSelector) {
431
+ throw new Error("Could not find text input area");
432
+ }
433
+ const textArea = await page.$(activeSelector);
403
434
  if (!textArea) {
404
435
  throw new Error("Could not find text input area");
405
436
  }
406
437
  // Use clipboard for large text
407
438
  if (text.length > 500) {
408
439
  await page.evaluate((t) => {
409
- // @ts-expect-error - navigator available in browser context
410
- navigator.clipboard.writeText(t);
440
+ const browser = globalThis;
441
+ browser.navigator.clipboard.writeText(t);
411
442
  }, text);
412
443
  await textArea.focus();
413
444
  await page.keyboard.down("Control");
@@ -415,28 +446,26 @@ export class SourceManager {
415
446
  await page.keyboard.up("Control");
416
447
  }
417
448
  else {
418
- await humanType(page, NOTEBOOKLM_SELECTORS.textInput.primary, text);
449
+ await humanType(page, activeSelector, text);
419
450
  }
420
451
  await randomDelay(500, 800);
421
452
  // Click Insert button — prefer locale-independent selectors
422
453
  const insertClicked = await page.evaluate(() => {
454
+ const browser = globalThis;
423
455
  // Primary: type=submit (locale-independent)
424
- // @ts-expect-error - DOM types
425
- const submitBtn = document.querySelector("button[type='submit']:not([disabled])");
456
+ const submitBtn = browser.document.querySelector("button[type='submit']:not([disabled])");
426
457
  if (submitBtn && submitBtn.offsetParent !== null) {
427
458
  submitBtn.click();
428
459
  return true;
429
460
  }
430
461
  // Secondary: primary color class (locale-independent NotebookLM convention)
431
- // @ts-expect-error - DOM types
432
- const primaryBtn = document.querySelector("button.button-color--primary:not([disabled])");
462
+ const primaryBtn = browser.document.querySelector("button.button-color--primary:not([disabled])");
433
463
  if (primaryBtn && primaryBtn.offsetParent !== null) {
434
464
  primaryBtn.click();
435
465
  return true;
436
466
  }
437
467
  // Fallback: text match (English only)
438
- // @ts-expect-error - DOM types
439
- const buttons = document.querySelectorAll("button");
468
+ const buttons = Array.from(browser.document.querySelectorAll("button"));
440
469
  for (const btn of buttons) {
441
470
  const text = btn.textContent?.toLowerCase() || "";
442
471
  if (text.includes("insert") || text.includes("add") || text.includes("submit")) {
@@ -450,6 +479,35 @@ export class SourceManager {
450
479
  throw new Error("Could not find Insert button");
451
480
  }
452
481
  }
482
+ async findValidTextInputSelectorOnPage(page) {
483
+ for (const selector of getSelectors("textInput")) {
484
+ try {
485
+ const state = await page.evaluate((targetSelector) => {
486
+ const browser = globalThis;
487
+ const textarea = browser.document.querySelector(targetSelector);
488
+ if (!textarea || textarea.offsetParent === null) {
489
+ return { matches: false };
490
+ }
491
+ const aria = textarea.getAttribute("aria-label")?.toLowerCase() || "";
492
+ const placeholder = textarea.getAttribute("placeholder")?.toLowerCase() || "";
493
+ const className = textarea.className?.toLowerCase() || "";
494
+ const haystack = `${aria} ${placeholder} ${className}`;
495
+ return {
496
+ matches: !haystack.includes("discover sources") &&
497
+ !haystack.includes("search the web") &&
498
+ !haystack.includes("query-box"),
499
+ };
500
+ }, selector);
501
+ if (state.matches) {
502
+ return selector;
503
+ }
504
+ }
505
+ catch (err) {
506
+ log.debug(`source-manager: validating standalone text source input selector: ${err instanceof Error ? err.message : String(err)}`);
507
+ }
508
+ }
509
+ return null;
510
+ }
453
511
  /**
454
512
  * Internal: Add file source
455
513
  * December 2025: NotebookLM creates a hidden input[type="file"] AFTER clicking
@@ -550,15 +608,14 @@ export class SourceManager {
550
608
  /**
551
609
  * Wait for source processing to complete
552
610
  */
553
- async waitForSourceProcessing(page, timeoutMs = 30000) {
611
+ async waitForSourceProcessing(page, timeoutMs = STANDARD_SOURCE_PROCESSING_TIMEOUT_MS) {
554
612
  const startTime = Date.now();
555
613
  while (Date.now() - startTime < timeoutMs) {
556
614
  const isProcessing = await page.evaluate(() => {
615
+ const browser = globalThis;
557
616
  // Look for processing indicators
558
- // @ts-expect-error - DOM types
559
- const progressBar = document.querySelector('[role="progressbar"]');
560
- // @ts-expect-error - DOM types
561
- const spinner = document.querySelector('[class*="spinner"], [class*="loading"]');
617
+ const progressBar = browser.document.querySelector('[role="progressbar"]');
618
+ const spinner = browser.document.querySelector('[class*="spinner"], [class*="loading"]');
562
619
  return !!(progressBar || spinner);
563
620
  });
564
621
  if (!isProcessing) {
@@ -576,11 +633,593 @@ export class SourceManager {
576
633
  try {
577
634
  await this.page.close();
578
635
  }
579
- catch {
636
+ catch (err) {
637
+ log.debug(`source-manager: closing page: ${err instanceof Error ? err.message : String(err)}`);
580
638
  // Ignore close errors
581
639
  }
582
640
  this.page = null;
583
641
  }
584
642
  }
585
643
  }
644
+ export class NotebookCreationSourceManager {
645
+ getPage;
646
+ constructor(getPage) {
647
+ this.getPage = getPage;
648
+ }
649
+ async addSource(source) {
650
+ const page = this.requirePage();
651
+ const expectedNotebookUrl = page.url();
652
+ log.info(`📍 Current notebook URL: ${expectedNotebookUrl}`);
653
+ const dialogAlreadyOpen = await this.isSourceDialogOpen();
654
+ log.info(`📋 Source dialog already open: ${dialogAlreadyOpen}`);
655
+ if (!dialogAlreadyOpen) {
656
+ await this.clickAddSource();
657
+ const currentUrl = page.url();
658
+ if (!currentUrl.includes("/notebook/") ||
659
+ (expectedNotebookUrl.includes("/notebook/") &&
660
+ !currentUrl.includes(expectedNotebookUrl.split("/notebook/")[1]?.split("?")[0] || ""))) {
661
+ log.error(`❌ URL changed unexpectedly! Expected: ${expectedNotebookUrl}, Got: ${currentUrl}`);
662
+ throw new Error("Navigation error: accidentally navigated away from notebook. This may indicate clicking wrong button.");
663
+ }
664
+ }
665
+ else {
666
+ log.info("📋 Source dialog already open - skipping clickAddSource");
667
+ }
668
+ switch (source.type) {
669
+ case "url":
670
+ await this.addUrlSource(source.value);
671
+ break;
672
+ case "text":
673
+ await this.addTextSource(source.value, source.title);
674
+ break;
675
+ case "file":
676
+ await this.addFileSource(source.value);
677
+ break;
678
+ default:
679
+ throw new Error(`Unknown source type: ${source.type}`);
680
+ }
681
+ log.info(`📍 URL after adding source: ${page.url()}`);
682
+ }
683
+ getSourceDescription(source) {
684
+ switch (source.type) {
685
+ case "url":
686
+ try {
687
+ const url = new URL(source.value);
688
+ return `URL: ${url.hostname}`;
689
+ }
690
+ catch (err) {
691
+ log.debug(`source-manager: parsing source URL in getSourceDescription: ${err instanceof Error ? err.message : String(err)}`);
692
+ return `URL: ${source.value.slice(0, 50)}`;
693
+ }
694
+ case "text":
695
+ return source.title || `Text: ${source.value.slice(0, 30)}...`;
696
+ case "file":
697
+ return `File: ${path.basename(source.value)}`;
698
+ default:
699
+ return "Unknown source";
700
+ }
701
+ }
702
+ requirePage() {
703
+ const page = this.getPage();
704
+ if (!page)
705
+ throw new Error("Page not initialized");
706
+ return page;
707
+ }
708
+ async isSourceDialogOpen() {
709
+ const page = this.getPage();
710
+ if (!page)
711
+ return false;
712
+ const dialogIndicators = await page.evaluate(() => {
713
+ const browser = globalThis;
714
+ const dialogs = Array.from(browser.document.querySelectorAll("mat-dialog-container"));
715
+ for (const d of dialogs) {
716
+ if (d.offsetParent !== null && d.textContent?.trim()) {
717
+ return { open: true, reason: "dialog_container_visible" };
718
+ }
719
+ }
720
+ const chipGroup = browser.document.querySelector("mat-chip-listbox, mat-chip-group, mat-chip-set");
721
+ if (chipGroup && chipGroup.offsetParent !== null) {
722
+ return { open: true, reason: "chip_group_visible" };
723
+ }
724
+ const dropzones = Array.from(browser.document.querySelectorAll('.dropzone, [class*="dropzone"]'));
725
+ if (dropzones.length > 0) {
726
+ for (const dz of dropzones) {
727
+ if (dz.offsetParent !== null) {
728
+ return { open: true, reason: "dropzone_visible" };
729
+ }
730
+ }
731
+ }
732
+ const uploadBtn = browser.document.querySelector('button[class*="upload"], input[type="file"]');
733
+ if (uploadBtn && uploadBtn.offsetParent !== null) {
734
+ return { open: true, reason: "upload_button_visible" };
735
+ }
736
+ return { open: false };
737
+ });
738
+ log.info(`📋 isSourceDialogOpen check: ${JSON.stringify(dialogIndicators)}`);
739
+ return dialogIndicators.open;
740
+ }
741
+ async tryAddSourceByAria(page) {
742
+ try {
743
+ let locator = page.locator('button[aria-label="Add source"]');
744
+ let count = await locator.count();
745
+ log.info(` aria "Add source": ${count} found`);
746
+ if (count === 0) {
747
+ locator = page.locator('button[aria-label="Add sources"]');
748
+ count = await locator.count();
749
+ log.info(` aria "Add sources": ${count} found`);
750
+ }
751
+ if (count > 0 && await locator.first().isVisible()) {
752
+ await locator.first().click();
753
+ await randomDelay(800, 1500);
754
+ return true;
755
+ }
756
+ }
757
+ catch (e) {
758
+ log.info(` aria approach: ${e}`);
759
+ }
760
+ return false;
761
+ }
762
+ async tryAddSourceByClass(page) {
763
+ try {
764
+ const locator = page.locator('button.add-source-button');
765
+ const count = await locator.count();
766
+ log.info(` class add-source-button: ${count} found`);
767
+ if (count > 0 && await locator.first().isVisible()) {
768
+ await locator.first().click();
769
+ await randomDelay(800, 1500);
770
+ return true;
771
+ }
772
+ }
773
+ catch (e) {
774
+ log.info(` class approach: ${e}`);
775
+ }
776
+ return false;
777
+ }
778
+ async tryAddSourceByJs(page) {
779
+ try {
780
+ const clicked = await page.evaluate(() => {
781
+ const browser = globalThis;
782
+ const elements = Array.from(browser.document.querySelectorAll('button, [role="button"]'));
783
+ for (const el of elements) {
784
+ const elText = el.textContent?.trim().toLowerCase() ?? "";
785
+ const ariaLabel = el.getAttribute("aria-label")?.toLowerCase() ?? "";
786
+ const className = el.className?.toLowerCase() ?? "";
787
+ if (ariaLabel.includes("create") || className.includes("create-notebook") ||
788
+ elText.includes("create") || elText.includes("add note") ||
789
+ className.includes("add-note"))
790
+ continue;
791
+ if (ariaLabel === "add source" || ariaLabel.includes("add source") ||
792
+ elText.includes("add source") || className.includes("add-source")) {
793
+ el.click();
794
+ return true;
795
+ }
796
+ }
797
+ return false;
798
+ });
799
+ if (clicked) {
800
+ await randomDelay(800, 1500);
801
+ return true;
802
+ }
803
+ }
804
+ catch (err) {
805
+ log.debug(`source-manager: clicking 'Add source' via JS: ${err instanceof Error ? err.message : String(err)}`);
806
+ }
807
+ return false;
808
+ }
809
+ async clickAddSource() {
810
+ const page = this.requirePage();
811
+ log.info("📎 Clicking 'Add source' button...");
812
+ const debugEnabled = process.env.DEBUG === "true";
813
+ if (debugEnabled) {
814
+ log.dim(` Current URL: ${page.url()}`);
815
+ }
816
+ await randomDelay(2000, 3000);
817
+ if (debugEnabled) {
818
+ const buttonsInfo = await page.evaluate(() => {
819
+ const browser = globalThis;
820
+ return Array.from(browser.document.querySelectorAll('button, [role="button"]'))
821
+ .filter((btn) => {
822
+ const aria = btn.getAttribute("aria-label")?.toLowerCase() ?? "";
823
+ const text = btn.textContent?.trim().toLowerCase() ?? "";
824
+ const cls = btn.className?.toLowerCase() ?? "";
825
+ return aria.includes("add") || aria.includes("create") || text.includes("add") ||
826
+ text.includes("create") || cls.includes("add") || cls.includes("create");
827
+ })
828
+ .map((btn) => ({
829
+ text: btn.textContent?.trim().substring(0, 50) ?? "",
830
+ aria: btn.getAttribute("aria-label") ?? "",
831
+ class: btn.className?.substring(0, 50) ?? "",
832
+ visible: btn.offsetParent !== null,
833
+ }));
834
+ });
835
+ log.dim(` Buttons found: ${JSON.stringify(buttonsInfo, null, 2)}`);
836
+ }
837
+ if (await this.tryAddSourceByAria(page))
838
+ return;
839
+ if (await this.tryAddSourceByClass(page))
840
+ return;
841
+ if (await this.tryAddSourceByJs(page))
842
+ return;
843
+ log.warning("⚠️ Add source button not found, waiting and retrying...");
844
+ await randomDelay(3000, 4000);
845
+ if (await this.tryAddSourceByAria(page))
846
+ return;
847
+ throw new Error("Could not find 'Add source' button after retry");
848
+ }
849
+ async addUrlSource(url) {
850
+ const page = this.requirePage();
851
+ log.info(`🔗 Adding URL source: ${url}`);
852
+ await this.clickSourceTypeByText(["Website", "webWebsite", "Link", "Discover sources"]);
853
+ await randomDelay(500, 1000);
854
+ const selectors = getSelectors("urlInput");
855
+ for (const selector of selectors) {
856
+ try {
857
+ const input = await page.$(selector);
858
+ if (input && await input.isVisible()) {
859
+ await humanType(page, selector, url, { withTypos: false });
860
+ await randomDelay(500, 1000);
861
+ await this.clickDialogSubmit();
862
+ await this.waitForSourceProcessing({ mode: "strict" });
863
+ return;
864
+ }
865
+ }
866
+ catch (err) {
867
+ log.debug(`source-manager: entering URL into source input selector: ${err instanceof Error ? err.message : String(err)}`);
868
+ }
869
+ }
870
+ throw new Error("Could not find URL input field");
871
+ }
872
+ async addTextSource(text, title) {
873
+ const page = this.requirePage();
874
+ log.info(`📝 Adding text source${title ? `: ${title}` : ""}`);
875
+ let clickedBySelector = false;
876
+ for (const selector of getSelectors("textSourceOption")) {
877
+ try {
878
+ const option = await page.$(selector);
879
+ if (option && await option.isVisible()) {
880
+ await option.click();
881
+ clickedBySelector = true;
882
+ log.success(`✅ Clicked text source option via selector: ${selector}`);
883
+ break;
884
+ }
885
+ }
886
+ catch (err) {
887
+ log.debug(`source-manager: clicking text source option selector: ${err instanceof Error ? err.message : String(err)}`);
888
+ }
889
+ }
890
+ if (!clickedBySelector) {
891
+ await this.clickSourceTypeByText(["Copied text", "Paste as text", "Paste"]);
892
+ }
893
+ await randomDelay(2000, 2500);
894
+ const activeSelector = await this.findValidTextInputSelector(page);
895
+ const textarea = activeSelector ? await page.$(activeSelector) : null;
896
+ if (!textarea || !activeSelector) {
897
+ const visibleOptions = await this.getVisibleSourceOptions(page);
898
+ log.warning(`⚠️ Visible source options after text-source click: ${JSON.stringify(visibleOptions)}`);
899
+ const visibleTextareas = await this.getVisibleTextareas(page);
900
+ log.warning(`⚠️ Visible textareas after text-source click: ${JSON.stringify(visibleTextareas)}`);
901
+ throw new Error("Could not find text input area");
902
+ }
903
+ const isVisible = await textarea.isVisible().catch(() => false);
904
+ if (!isVisible) {
905
+ await randomDelay(1000, 1500);
906
+ }
907
+ await textarea.click();
908
+ await randomDelay(200, 400);
909
+ if (text.length > 500) {
910
+ await page.evaluate((clipboardText) => {
911
+ const browser = globalThis;
912
+ browser.navigator.clipboard.writeText(clipboardText);
913
+ }, text);
914
+ await page.keyboard.press("Control+V");
915
+ }
916
+ else {
917
+ await humanType(page, activeSelector, text, { withTypos: false });
918
+ }
919
+ await page.evaluate(({ selector, value }) => {
920
+ const browser = globalThis;
921
+ const textarea = browser.document.querySelector(selector);
922
+ if (!textarea)
923
+ return false;
924
+ textarea.focus();
925
+ if (typeof textarea.value === "string") {
926
+ textarea.value = value;
927
+ }
928
+ else {
929
+ textarea.textContent = value;
930
+ }
931
+ textarea.dispatchEvent(new browser.Event("input", { bubbles: true }));
932
+ textarea.dispatchEvent(new browser.Event("change", { bubbles: true }));
933
+ return true;
934
+ }, { selector: activeSelector, value: text });
935
+ await randomDelay(500, 1000);
936
+ const textState = await page.evaluate(({ selector }) => {
937
+ const browser = globalThis;
938
+ const textarea = browser.document.querySelector(selector);
939
+ const buttons = Array.from(browser.document.querySelectorAll("button")).map((button) => ({
940
+ text: button.textContent?.trim() || "",
941
+ disabled: button.getAttribute("disabled") !== null || button.getAttribute("aria-disabled") === "true",
942
+ }));
943
+ return {
944
+ textLength: typeof textarea?.value === "string"
945
+ ? textarea.value.length
946
+ : (textarea?.textContent?.length || 0),
947
+ textareaClass: textarea?.className || "",
948
+ textareaAria: textarea?.getAttribute?.("aria-label") || "",
949
+ textareaPlaceholder: textarea?.getAttribute?.("placeholder") || "",
950
+ visibleDialogs: Array.from(browser.document.querySelectorAll("[role='dialog'], mat-dialog-container, .cdk-overlay-pane"))
951
+ .filter((dialog) => dialog.offsetParent !== null)
952
+ .map((dialog) => (dialog.textContent || "").trim().slice(0, 200))
953
+ .filter((text) => text.length > 0)
954
+ .slice(0, 4),
955
+ buttons: buttons.filter((button) => button.text.length > 0).slice(0, 12),
956
+ };
957
+ }, { selector: activeSelector });
958
+ log.info(`🧪 Text source state before insert: ${JSON.stringify(textState)}`);
959
+ await this.clickDialogSubmit();
960
+ await this.waitForSourceProcessing({ mode: "lenient" });
961
+ }
962
+ async tryFileUploadViaTrigger(page, locatorStr, absolutePath) {
963
+ const locator = page.locator(locatorStr);
964
+ if (await locator.count() > 0 && await locator.first().isVisible()) {
965
+ await locator.first().click();
966
+ await page.waitForSelector('input[type="file"]', { timeout: 5000, state: 'attached' });
967
+ await randomDelay(200, 400);
968
+ await page.locator('input[type="file"]').first().setInputFiles(absolutePath);
969
+ await randomDelay(1000, 2000);
970
+ await this.waitForSourceProcessing({ mode: "lenient" });
971
+ return true;
972
+ }
973
+ return false;
974
+ }
975
+ async addFileSource(filePath) {
976
+ const page = this.requirePage();
977
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
978
+ if (!fs.existsSync(absolutePath)) {
979
+ throw new Error(`File not found: ${absolutePath}`);
980
+ }
981
+ log.info(`📁 Adding file source: ${path.basename(absolutePath)}`);
982
+ await randomDelay(500, 1000);
983
+ try {
984
+ if (await this.tryFileUploadViaTrigger(page, 'span.dropzone__file-dialog-button', absolutePath))
985
+ return;
986
+ if (await this.tryFileUploadViaTrigger(page, '[xapscottyuploadertrigger]', absolutePath))
987
+ return;
988
+ if (await this.tryFileUploadViaTrigger(page, 'button[class*="upload"], button[aria-label="Upload sources from your computer"]', absolutePath))
989
+ return;
990
+ }
991
+ catch (e) {
992
+ log.info(` Playwright click approach: ${e}`);
993
+ }
994
+ try {
995
+ const fileChooserPromise = page.waitForEvent('filechooser', { timeout: 5000 });
996
+ const chooseFileLocator = page.locator('span.dropzone__file-dialog-button');
997
+ if (await chooseFileLocator.count() > 0) {
998
+ await chooseFileLocator.first().click();
999
+ }
1000
+ const fileChooser = await fileChooserPromise;
1001
+ await fileChooser.setFiles(absolutePath);
1002
+ log.success(" ✅ File uploaded via filechooser event");
1003
+ await randomDelay(1000, 2000);
1004
+ await this.waitForSourceProcessing({ mode: "lenient" });
1005
+ return;
1006
+ }
1007
+ catch (e) {
1008
+ log.info(` Filechooser approach: ${e}`);
1009
+ }
1010
+ try {
1011
+ const fileInputLocator = page.locator('input[type="file"]');
1012
+ if (await fileInputLocator.count() > 0) {
1013
+ await fileInputLocator.first().setInputFiles(absolutePath);
1014
+ log.success(" ✅ File uploaded via existing locator");
1015
+ await randomDelay(1000, 2000);
1016
+ await this.waitForSourceProcessing({ mode: "lenient" });
1017
+ return;
1018
+ }
1019
+ }
1020
+ catch (e) {
1021
+ log.info(` Existing locator attempt: ${e}`);
1022
+ }
1023
+ throw new Error("Could not upload file - all methods failed. NotebookLM may be using an unsupported upload method.");
1024
+ }
1025
+ async clickSourceTypeByText(textPatterns) {
1026
+ const page = this.requirePage();
1027
+ for (const pattern of textPatterns) {
1028
+ try {
1029
+ const clicked = await page.evaluate((searchText) => {
1030
+ const browser = globalThis;
1031
+ const elements = Array.from(browser.document.querySelectorAll('button, [role="button"], mat-chip, mat-chip-option, [mat-chip-option]'));
1032
+ for (const el of elements) {
1033
+ const text = el.textContent?.trim() || "";
1034
+ const aria = el.getAttribute("aria-label")?.trim() || "";
1035
+ const haystack = `${text} ${aria}`.toLowerCase();
1036
+ if (haystack.includes("search the web") || haystack.includes("discover sources")) {
1037
+ continue;
1038
+ }
1039
+ if ((text === searchText || haystack.includes(searchText.toLowerCase())) &&
1040
+ el.offsetParent !== null) {
1041
+ el.click();
1042
+ return true;
1043
+ }
1044
+ }
1045
+ return false;
1046
+ }, pattern);
1047
+ if (clicked) {
1048
+ log.success(`✅ Clicked source type: ${pattern}`);
1049
+ await randomDelay(800, 1200);
1050
+ return;
1051
+ }
1052
+ }
1053
+ catch (err) {
1054
+ log.debug(`source-manager: clicking source type tab via text pattern: ${err instanceof Error ? err.message : String(err)}`);
1055
+ }
1056
+ }
1057
+ const visibleOptions = await this.getVisibleSourceOptions(this.requirePage());
1058
+ log.warning(`⚠️ Could not find source type: ${textPatterns.join(", ")} — visible: ${JSON.stringify(visibleOptions)}`);
1059
+ }
1060
+ async findValidTextInputSelector(page) {
1061
+ for (const selector of getSelectors("textInput")) {
1062
+ try {
1063
+ const state = await page.evaluate((targetSelector) => {
1064
+ const browser = globalThis;
1065
+ const textarea = browser.document.querySelector(targetSelector);
1066
+ if (!textarea || textarea.offsetParent === null) {
1067
+ return { matches: false, rejected: "missing" };
1068
+ }
1069
+ const aria = textarea.getAttribute("aria-label")?.toLowerCase() || "";
1070
+ const placeholder = textarea.getAttribute("placeholder")?.toLowerCase() || "";
1071
+ const className = textarea.className?.toLowerCase() || "";
1072
+ const haystack = `${aria} ${placeholder} ${className}`;
1073
+ if (haystack.includes("discover sources") ||
1074
+ haystack.includes("search the web") ||
1075
+ haystack.includes("query-box")) {
1076
+ return { matches: false, rejected: haystack };
1077
+ }
1078
+ return { matches: true, rejected: "" };
1079
+ }, selector);
1080
+ if (state.matches) {
1081
+ return selector;
1082
+ }
1083
+ }
1084
+ catch (err) {
1085
+ log.debug(`source-manager: validating text source input selector: ${err instanceof Error ? err.message : String(err)}`);
1086
+ }
1087
+ }
1088
+ return null;
1089
+ }
1090
+ async getVisibleSourceOptions(page) {
1091
+ try {
1092
+ return await page.evaluate(() => {
1093
+ const browser = globalThis;
1094
+ return Array.from(browser.document.querySelectorAll('button, [role="button"], mat-chip, mat-chip-option, [mat-chip-option]'))
1095
+ .map((el) => el)
1096
+ .filter((el) => el.offsetParent !== null)
1097
+ .map((el) => `${el.textContent?.trim() || ""} ${el.getAttribute("aria-label") || ""}`.trim())
1098
+ .filter((text) => text.length > 0)
1099
+ .slice(0, 20);
1100
+ });
1101
+ }
1102
+ catch (err) {
1103
+ log.debug(`source-manager: collecting visible source options: ${err instanceof Error ? err.message : String(err)}`);
1104
+ return [];
1105
+ }
1106
+ }
1107
+ async getVisibleTextareas(page) {
1108
+ try {
1109
+ return await page.evaluate(() => {
1110
+ const browser = globalThis;
1111
+ return Array.from(browser.document.querySelectorAll("textarea"))
1112
+ .map((el) => el)
1113
+ .filter((el) => el.offsetParent !== null)
1114
+ .map((el) => ({
1115
+ selector: `textarea.${el.className?.replace(/\s+/g, ".")}` || "textarea",
1116
+ aria: el.getAttribute("aria-label") || "",
1117
+ placeholder: el.getAttribute("placeholder") || "",
1118
+ classes: el.className || "",
1119
+ }));
1120
+ });
1121
+ }
1122
+ catch (err) {
1123
+ log.debug(`source-manager: collecting visible textareas: ${err instanceof Error ? err.message : String(err)}`);
1124
+ return [];
1125
+ }
1126
+ }
1127
+ async clickDialogSubmit() {
1128
+ const page = this.requirePage();
1129
+ // Strategy 1: JS-based click (works when elements are partially obscured)
1130
+ const clicked = await page.evaluate(() => {
1131
+ const browser = globalThis;
1132
+ const submitBtn = browser.document.querySelector("button[type='submit']:not([disabled])");
1133
+ if (submitBtn && submitBtn.offsetParent !== null) {
1134
+ submitBtn.click();
1135
+ return true;
1136
+ }
1137
+ const primaryBtn = browser.document.querySelector("button.button-color--primary:not([disabled])");
1138
+ if (primaryBtn && primaryBtn.offsetParent !== null) {
1139
+ primaryBtn.click();
1140
+ return true;
1141
+ }
1142
+ const buttons = Array.from(browser.document.querySelectorAll("button"));
1143
+ for (const btn of buttons) {
1144
+ const text = btn.textContent?.trim() ?? "";
1145
+ if (text === "Insert" || text.toLowerCase() === "insert") {
1146
+ btn.click();
1147
+ return true;
1148
+ }
1149
+ }
1150
+ return false;
1151
+ });
1152
+ if (clicked)
1153
+ return;
1154
+ // Strategy 2: Patchright selector-based click
1155
+ for (const selector of getSelectors("submitButton")) {
1156
+ try {
1157
+ const element = await page.$(selector);
1158
+ if (element && await element.isVisible()) {
1159
+ await element.click();
1160
+ return;
1161
+ }
1162
+ }
1163
+ catch (err) {
1164
+ log.debug(`source-manager: clicking submit button selector: ${err instanceof Error ? err.message : String(err)}`);
1165
+ }
1166
+ }
1167
+ // Final fallback
1168
+ await page.keyboard.press("Enter");
1169
+ }
1170
+ async waitForSourceProcessing(options = { mode: "strict" }) {
1171
+ const page = this.requirePage();
1172
+ log.info("⏳ Waiting for source processing...");
1173
+ if (options.mode === "lenient") {
1174
+ await randomDelay(3000, 4000);
1175
+ const dialogStillOpen = await this.isSourceDialogOpen();
1176
+ if (!dialogStillOpen) {
1177
+ log.success("✅ Source dialog closed - assuming success");
1178
+ return;
1179
+ }
1180
+ await this.throwIfSourceErrorAlert(page);
1181
+ await randomDelay(2000, 3000);
1182
+ log.success("✅ Source processing appears complete");
1183
+ return;
1184
+ }
1185
+ const timeout = LENIENT_SOURCE_PROCESSING_TIMEOUT_MS;
1186
+ const startTime = Date.now();
1187
+ while (Date.now() - startTime < timeout) {
1188
+ const successElement = await findElement(page, "successIndicator");
1189
+ if (successElement) {
1190
+ log.success("✅ Source processed successfully");
1191
+ return;
1192
+ }
1193
+ const errorElement = await findElement(page, "errorMessage");
1194
+ if (errorElement) {
1195
+ const errorText = (await errorElement.innerText?.()) ||
1196
+ "Unknown error";
1197
+ throw new Error(`Source processing failed: ${errorText}`);
1198
+ }
1199
+ const processingElement = await findElement(page, "processingIndicator");
1200
+ if (!processingElement) {
1201
+ await randomDelay(1000, 1500);
1202
+ return;
1203
+ }
1204
+ await page.waitForTimeout(1000);
1205
+ }
1206
+ log.warning("⚠️ Source processing timeout - continuing anyway");
1207
+ }
1208
+ async throwIfSourceErrorAlert(page) {
1209
+ const hasError = await page.evaluate(() => {
1210
+ const browser = globalThis;
1211
+ const alerts = Array.from(browser.document.querySelectorAll('[role="alert"]'));
1212
+ for (const alert of alerts) {
1213
+ const text = alert.textContent?.toLowerCase() || "";
1214
+ if (text.includes("error") || text.includes("failed") || text.includes("invalid") || text.includes("unable")) {
1215
+ return text.substring(0, 100);
1216
+ }
1217
+ }
1218
+ return null;
1219
+ });
1220
+ if (hasError) {
1221
+ throw new Error(`Source processing failed: ${hasError}`);
1222
+ }
1223
+ }
1224
+ }
586
1225
  //# sourceMappingURL=source-manager.js.map