@pan-sec/notebooklm-mcp 2026.2.11 → 2026.3.0

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