@pan-sec/notebooklm-mcp 2026.3.3 → 2026.4.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 (470) hide show
  1. package/dist/auth/auth-manager.d.ts +0 -1
  2. package/dist/auth/auth-manager.js +0 -1
  3. package/dist/auth/mcp-auth.d.ts +0 -1
  4. package/dist/auth/mcp-auth.js +0 -1
  5. package/dist/compliance/alert-manager.d.ts +6 -2
  6. package/dist/compliance/alert-manager.js +40 -10
  7. package/dist/compliance/breach-detection.d.ts +0 -1
  8. package/dist/compliance/breach-detection.js +0 -1
  9. package/dist/compliance/change-log.d.ts +13 -1
  10. package/dist/compliance/change-log.js +82 -16
  11. package/dist/compliance/compliance-logger.d.ts +29 -3
  12. package/dist/compliance/compliance-logger.js +90 -27
  13. package/dist/compliance/compliance-tools.d.ts +0 -1
  14. package/dist/compliance/compliance-tools.js +0 -1
  15. package/dist/compliance/consent-manager.d.ts +0 -1
  16. package/dist/compliance/consent-manager.js +0 -1
  17. package/dist/compliance/dashboard.d.ts +4 -3
  18. package/dist/compliance/dashboard.js +11 -8
  19. package/dist/compliance/data-classification.d.ts +0 -1
  20. package/dist/compliance/data-classification.js +0 -1
  21. package/dist/compliance/data-erasure.d.ts +0 -1
  22. package/dist/compliance/data-erasure.js +0 -1
  23. package/dist/compliance/data-export.d.ts +0 -1
  24. package/dist/compliance/data-export.js +0 -1
  25. package/dist/compliance/data-inventory.d.ts +0 -1
  26. package/dist/compliance/data-inventory.js +0 -1
  27. package/dist/compliance/dsar-handler.d.ts +0 -1
  28. package/dist/compliance/dsar-handler.js +0 -1
  29. package/dist/compliance/evidence-collector.d.ts +0 -1
  30. package/dist/compliance/evidence-collector.js +4 -2
  31. package/dist/compliance/health-monitor.d.ts +0 -1
  32. package/dist/compliance/health-monitor.js +0 -1
  33. package/dist/compliance/incident-manager.d.ts +0 -1
  34. package/dist/compliance/incident-manager.js +0 -1
  35. package/dist/compliance/index.d.ts +0 -1
  36. package/dist/compliance/index.js +0 -1
  37. package/dist/compliance/policy-docs.d.ts +0 -1
  38. package/dist/compliance/policy-docs.js +0 -1
  39. package/dist/compliance/privacy-notice-text.d.ts +0 -1
  40. package/dist/compliance/privacy-notice-text.js +0 -1
  41. package/dist/compliance/privacy-notice.d.ts +0 -1
  42. package/dist/compliance/privacy-notice.js +0 -1
  43. package/dist/compliance/report-generator.d.ts +7 -1
  44. package/dist/compliance/report-generator.js +116 -34
  45. package/dist/compliance/retention-engine.d.ts +0 -1
  46. package/dist/compliance/retention-engine.js +0 -1
  47. package/dist/compliance/siem-exporter.d.ts +26 -2
  48. package/dist/compliance/siem-exporter.js +89 -24
  49. package/dist/compliance/types.d.ts +0 -1
  50. package/dist/compliance/types.js +0 -1
  51. package/dist/config.d.ts +0 -1
  52. package/dist/config.js +2 -3
  53. package/dist/errors.d.ts +0 -1
  54. package/dist/errors.js +0 -1
  55. package/dist/events/event-emitter.d.ts +9 -1
  56. package/dist/events/event-emitter.js +47 -8
  57. package/dist/events/event-types.d.ts +0 -1
  58. package/dist/events/event-types.js +8 -2
  59. package/dist/gemini/gemini-client.d.ts +0 -1
  60. package/dist/gemini/gemini-client.js +237 -45
  61. package/dist/gemini/index.d.ts +0 -1
  62. package/dist/gemini/index.js +0 -1
  63. package/dist/gemini/pdf-chunker.d.ts +0 -1
  64. package/dist/gemini/pdf-chunker.js +60 -35
  65. package/dist/gemini/types.d.ts +0 -1
  66. package/dist/gemini/types.js +0 -1
  67. package/dist/index.d.ts +0 -1
  68. package/dist/index.js +60 -7
  69. package/dist/library/notebook-library.d.ts +30 -2
  70. package/dist/library/notebook-library.js +345 -85
  71. package/dist/library/types.d.ts +0 -1
  72. package/dist/library/types.js +0 -1
  73. package/dist/logging/index.d.ts +0 -1
  74. package/dist/logging/index.js +0 -1
  75. package/dist/logging/query-logger.d.ts +20 -1
  76. package/dist/logging/query-logger.js +104 -21
  77. package/dist/notebook-creation/audio-manager.d.ts +7 -16
  78. package/dist/notebook-creation/audio-manager.js +115 -58
  79. package/dist/notebook-creation/browser-options.d.ts +0 -1
  80. package/dist/notebook-creation/browser-options.js +0 -1
  81. package/dist/notebook-creation/data-table-manager.d.ts +8 -14
  82. package/dist/notebook-creation/data-table-manager.js +64 -37
  83. package/dist/notebook-creation/dom-scripts.d.ts +0 -1
  84. package/dist/notebook-creation/dom-scripts.js +0 -1
  85. package/dist/notebook-creation/errors.d.ts +0 -1
  86. package/dist/notebook-creation/errors.js +0 -1
  87. package/dist/notebook-creation/index.d.ts +0 -1
  88. package/dist/notebook-creation/index.js +0 -1
  89. package/dist/notebook-creation/notebook-creator.d.ts +9 -1
  90. package/dist/notebook-creation/notebook-creator.js +50 -1
  91. package/dist/notebook-creation/notebook-nav.d.ts +0 -1
  92. package/dist/notebook-creation/notebook-nav.js +21 -6
  93. package/dist/notebook-creation/notebook-sync.d.ts +14 -2
  94. package/dist/notebook-creation/notebook-sync.js +124 -35
  95. package/dist/notebook-creation/selectors.d.ts +0 -1
  96. package/dist/notebook-creation/selectors.js +6 -4
  97. package/dist/notebook-creation/source-manager-shared.d.ts +75 -0
  98. package/dist/notebook-creation/source-manager-shared.js +86 -0
  99. package/dist/notebook-creation/source-manager.d.ts +29 -2
  100. package/dist/notebook-creation/source-manager.js +0 -0
  101. package/dist/notebook-creation/studio-manager-base.d.ts +57 -0
  102. package/dist/notebook-creation/studio-manager-base.js +67 -0
  103. package/dist/notebook-creation/types.d.ts +0 -1
  104. package/dist/notebook-creation/types.js +0 -1
  105. package/dist/notebook-creation/video-manager.d.ts +3 -16
  106. package/dist/notebook-creation/video-manager.js +94 -53
  107. package/dist/observability/metrics.d.ts +0 -1
  108. package/dist/observability/metrics.js +0 -1
  109. package/dist/quota/index.d.ts +0 -1
  110. package/dist/quota/index.js +0 -1
  111. package/dist/quota/quota-manager.d.ts +59 -4
  112. package/dist/quota/quota-manager.js +195 -46
  113. package/dist/resources/resource-handlers.d.ts +0 -1
  114. package/dist/resources/resource-handlers.js +33 -3
  115. package/dist/session/browser-session.d.ts +0 -1
  116. package/dist/session/browser-session.js +0 -1
  117. package/dist/session/session-manager.d.ts +0 -1
  118. package/dist/session/session-manager.js +0 -1
  119. package/dist/session/session-timeout.d.ts +0 -1
  120. package/dist/session/session-timeout.js +0 -1
  121. package/dist/session/shared-context-manager.d.ts +0 -1
  122. package/dist/session/shared-context-manager.js +0 -1
  123. package/dist/tools/annotations.d.ts +0 -1
  124. package/dist/tools/annotations.js +0 -1
  125. package/dist/tools/definitions/ask-question.d.ts +6 -3
  126. package/dist/tools/definitions/ask-question.js +12 -8
  127. package/dist/tools/definitions/chat-history.d.ts +0 -1
  128. package/dist/tools/definitions/chat-history.js +1 -1
  129. package/dist/tools/definitions/data-tables.d.ts +0 -1
  130. package/dist/tools/definitions/data-tables.js +4 -1
  131. package/dist/tools/definitions/gemini.d.ts +0 -1
  132. package/dist/tools/definitions/gemini.js +14 -7
  133. package/dist/tools/definitions/notebook-management.d.ts +0 -1
  134. package/dist/tools/definitions/notebook-management.js +7 -2
  135. package/dist/tools/definitions/query-history.d.ts +0 -1
  136. package/dist/tools/definitions/query-history.js +0 -1
  137. package/dist/tools/definitions/session-management.d.ts +0 -1
  138. package/dist/tools/definitions/session-management.js +0 -1
  139. package/dist/tools/definitions/system.d.ts +0 -1
  140. package/dist/tools/definitions/system.js +32 -12
  141. package/dist/tools/definitions/video.d.ts +0 -1
  142. package/dist/tools/definitions/video.js +6 -3
  143. package/dist/tools/definitions.d.ts +0 -1
  144. package/dist/tools/definitions.js +0 -1
  145. package/dist/tools/handlers/ask-question.d.ts +0 -1
  146. package/dist/tools/handlers/ask-question.js +47 -18
  147. package/dist/tools/handlers/audio-video.d.ts +0 -1
  148. package/dist/tools/handlers/audio-video.js +0 -1
  149. package/dist/tools/handlers/auth.d.ts +0 -1
  150. package/dist/tools/handlers/auth.js +0 -1
  151. package/dist/tools/handlers/error-utils.d.ts +0 -1
  152. package/dist/tools/handlers/error-utils.js +0 -1
  153. package/dist/tools/handlers/gemini.d.ts +0 -1
  154. package/dist/tools/handlers/gemini.js +0 -1
  155. package/dist/tools/handlers/index.d.ts +0 -1
  156. package/dist/tools/handlers/index.js +0 -1
  157. package/dist/tools/handlers/notebook-creation.d.ts +0 -1
  158. package/dist/tools/handlers/notebook-creation.js +16 -1
  159. package/dist/tools/handlers/notebook-management.d.ts +0 -1
  160. package/dist/tools/handlers/notebook-management.js +7 -2
  161. package/dist/tools/handlers/session-management.d.ts +0 -1
  162. package/dist/tools/handlers/session-management.js +0 -1
  163. package/dist/tools/handlers/system.d.ts +0 -1
  164. package/dist/tools/handlers/system.js +0 -1
  165. package/dist/tools/handlers/types.d.ts +0 -1
  166. package/dist/tools/handlers/types.js +0 -1
  167. package/dist/tools/handlers/webhooks.d.ts +0 -1
  168. package/dist/tools/handlers/webhooks.js +0 -1
  169. package/dist/tools/icons.d.ts +0 -1
  170. package/dist/tools/icons.js +0 -1
  171. package/dist/tools/index.d.ts +0 -1
  172. package/dist/tools/index.js +0 -1
  173. package/dist/types.d.ts +0 -1
  174. package/dist/types.js +0 -1
  175. package/dist/utils/audit-logger.d.ts +11 -1
  176. package/dist/utils/audit-logger.js +189 -21
  177. package/dist/utils/cleanup-manager.d.ts +0 -1
  178. package/dist/utils/cleanup-manager.js +0 -1
  179. package/dist/utils/cli-handler.d.ts +0 -1
  180. package/dist/utils/cli-handler.js +0 -1
  181. package/dist/utils/crypto.d.ts +18 -9
  182. package/dist/utils/crypto.js +93 -28
  183. package/dist/utils/file-lock.d.ts +15 -1
  184. package/dist/utils/file-lock.js +67 -59
  185. package/dist/utils/file-permissions.d.ts +0 -1
  186. package/dist/utils/file-permissions.js +35 -7
  187. package/dist/utils/logger.d.ts +0 -1
  188. package/dist/utils/logger.js +0 -1
  189. package/dist/utils/page-utils.d.ts +0 -1
  190. package/dist/utils/page-utils.js +32 -28
  191. package/dist/utils/response-validator.d.ts +0 -1
  192. package/dist/utils/response-validator.js +18 -15
  193. package/dist/utils/secrets-scanner.d.ts +0 -1
  194. package/dist/utils/secrets-scanner.js +32 -7
  195. package/dist/utils/secure-memory.d.ts +34 -16
  196. package/dist/utils/secure-memory.js +40 -25
  197. package/dist/utils/security.d.ts +0 -1
  198. package/dist/utils/security.js +66 -39
  199. package/dist/utils/settings-manager.d.ts +9 -1
  200. package/dist/utils/settings-manager.js +45 -2
  201. package/dist/utils/stealth-utils.d.ts +0 -1
  202. package/dist/utils/stealth-utils.js +11 -9
  203. package/dist/webhooks/index.d.ts +0 -1
  204. package/dist/webhooks/index.js +0 -1
  205. package/dist/webhooks/types.d.ts +0 -1
  206. package/dist/webhooks/types.js +0 -1
  207. package/dist/webhooks/webhook-dispatcher.d.ts +0 -1
  208. package/dist/webhooks/webhook-dispatcher.js +0 -1
  209. package/package.json +5 -4
  210. package/dist/auth/auth-manager.d.ts.map +0 -1
  211. package/dist/auth/auth-manager.js.map +0 -1
  212. package/dist/auth/mcp-auth.d.ts.map +0 -1
  213. package/dist/auth/mcp-auth.js.map +0 -1
  214. package/dist/compliance/alert-manager.d.ts.map +0 -1
  215. package/dist/compliance/alert-manager.js.map +0 -1
  216. package/dist/compliance/breach-detection.d.ts.map +0 -1
  217. package/dist/compliance/breach-detection.js.map +0 -1
  218. package/dist/compliance/change-log.d.ts.map +0 -1
  219. package/dist/compliance/change-log.js.map +0 -1
  220. package/dist/compliance/compliance-logger.d.ts.map +0 -1
  221. package/dist/compliance/compliance-logger.js.map +0 -1
  222. package/dist/compliance/compliance-tools.d.ts.map +0 -1
  223. package/dist/compliance/compliance-tools.js.map +0 -1
  224. package/dist/compliance/consent-manager.d.ts.map +0 -1
  225. package/dist/compliance/consent-manager.js.map +0 -1
  226. package/dist/compliance/dashboard.d.ts.map +0 -1
  227. package/dist/compliance/dashboard.js.map +0 -1
  228. package/dist/compliance/data-classification.d.ts.map +0 -1
  229. package/dist/compliance/data-classification.js.map +0 -1
  230. package/dist/compliance/data-erasure.d.ts.map +0 -1
  231. package/dist/compliance/data-erasure.js.map +0 -1
  232. package/dist/compliance/data-export.d.ts.map +0 -1
  233. package/dist/compliance/data-export.js.map +0 -1
  234. package/dist/compliance/data-inventory.d.ts.map +0 -1
  235. package/dist/compliance/data-inventory.js.map +0 -1
  236. package/dist/compliance/dsar-handler.d.ts.map +0 -1
  237. package/dist/compliance/dsar-handler.js.map +0 -1
  238. package/dist/compliance/evidence-collector.d.ts.map +0 -1
  239. package/dist/compliance/evidence-collector.js.map +0 -1
  240. package/dist/compliance/health-monitor.d.ts.map +0 -1
  241. package/dist/compliance/health-monitor.js.map +0 -1
  242. package/dist/compliance/incident-manager.d.ts.map +0 -1
  243. package/dist/compliance/incident-manager.js.map +0 -1
  244. package/dist/compliance/index.d.ts.map +0 -1
  245. package/dist/compliance/index.js.map +0 -1
  246. package/dist/compliance/policy-docs.d.ts.map +0 -1
  247. package/dist/compliance/policy-docs.js.map +0 -1
  248. package/dist/compliance/privacy-notice-text.d.ts.map +0 -1
  249. package/dist/compliance/privacy-notice-text.js.map +0 -1
  250. package/dist/compliance/privacy-notice.d.ts.map +0 -1
  251. package/dist/compliance/privacy-notice.js.map +0 -1
  252. package/dist/compliance/report-generator.d.ts.map +0 -1
  253. package/dist/compliance/report-generator.js.map +0 -1
  254. package/dist/compliance/retention-engine.d.ts.map +0 -1
  255. package/dist/compliance/retention-engine.js.map +0 -1
  256. package/dist/compliance/siem-exporter.d.ts.map +0 -1
  257. package/dist/compliance/siem-exporter.js.map +0 -1
  258. package/dist/compliance/types.d.ts.map +0 -1
  259. package/dist/compliance/types.js.map +0 -1
  260. package/dist/config.d.ts.map +0 -1
  261. package/dist/config.js.map +0 -1
  262. package/dist/errors.d.ts.map +0 -1
  263. package/dist/errors.js.map +0 -1
  264. package/dist/events/event-emitter.d.ts.map +0 -1
  265. package/dist/events/event-emitter.js.map +0 -1
  266. package/dist/events/event-types.d.ts.map +0 -1
  267. package/dist/events/event-types.js.map +0 -1
  268. package/dist/gemini/gemini-client.d.ts.map +0 -1
  269. package/dist/gemini/gemini-client.js.map +0 -1
  270. package/dist/gemini/index.d.ts.map +0 -1
  271. package/dist/gemini/index.js.map +0 -1
  272. package/dist/gemini/pdf-chunker.d.ts.map +0 -1
  273. package/dist/gemini/pdf-chunker.js.map +0 -1
  274. package/dist/gemini/types.d.ts.map +0 -1
  275. package/dist/gemini/types.js.map +0 -1
  276. package/dist/index.d.ts.map +0 -1
  277. package/dist/index.js.map +0 -1
  278. package/dist/library/notebook-library.d.ts.map +0 -1
  279. package/dist/library/notebook-library.js.map +0 -1
  280. package/dist/library/types.d.ts.map +0 -1
  281. package/dist/library/types.js.map +0 -1
  282. package/dist/logging/index.d.ts.map +0 -1
  283. package/dist/logging/index.js.map +0 -1
  284. package/dist/logging/query-logger.d.ts.map +0 -1
  285. package/dist/logging/query-logger.js.map +0 -1
  286. package/dist/notebook-creation/audio-manager.d.ts.map +0 -1
  287. package/dist/notebook-creation/audio-manager.js.map +0 -1
  288. package/dist/notebook-creation/browser-options.d.ts.map +0 -1
  289. package/dist/notebook-creation/browser-options.js.map +0 -1
  290. package/dist/notebook-creation/data-table-manager.d.ts.map +0 -1
  291. package/dist/notebook-creation/data-table-manager.js.map +0 -1
  292. package/dist/notebook-creation/discover-creation-flow.d.ts +0 -2
  293. package/dist/notebook-creation/discover-creation-flow.d.ts.map +0 -1
  294. package/dist/notebook-creation/discover-creation-flow.js +0 -177
  295. package/dist/notebook-creation/discover-creation-flow.js.map +0 -1
  296. package/dist/notebook-creation/discover-quota.d.ts +0 -2
  297. package/dist/notebook-creation/discover-quota.d.ts.map +0 -1
  298. package/dist/notebook-creation/discover-quota.js +0 -194
  299. package/dist/notebook-creation/discover-quota.js.map +0 -1
  300. package/dist/notebook-creation/discover-source-dialog.d.ts +0 -8
  301. package/dist/notebook-creation/discover-source-dialog.d.ts.map +0 -1
  302. package/dist/notebook-creation/discover-source-dialog.js +0 -134
  303. package/dist/notebook-creation/discover-source-dialog.js.map +0 -1
  304. package/dist/notebook-creation/discover-sources.d.ts +0 -8
  305. package/dist/notebook-creation/discover-sources.d.ts.map +0 -1
  306. package/dist/notebook-creation/discover-sources.js +0 -272
  307. package/dist/notebook-creation/discover-sources.js.map +0 -1
  308. package/dist/notebook-creation/discover-text-input.d.ts +0 -7
  309. package/dist/notebook-creation/discover-text-input.d.ts.map +0 -1
  310. package/dist/notebook-creation/discover-text-input.js +0 -135
  311. package/dist/notebook-creation/discover-text-input.js.map +0 -1
  312. package/dist/notebook-creation/dom-scripts.d.ts.map +0 -1
  313. package/dist/notebook-creation/dom-scripts.js.map +0 -1
  314. package/dist/notebook-creation/errors.d.ts.map +0 -1
  315. package/dist/notebook-creation/errors.js.map +0 -1
  316. package/dist/notebook-creation/index.d.ts.map +0 -1
  317. package/dist/notebook-creation/index.js.map +0 -1
  318. package/dist/notebook-creation/notebook-creator.d.ts.map +0 -1
  319. package/dist/notebook-creation/notebook-creator.js.map +0 -1
  320. package/dist/notebook-creation/notebook-nav.d.ts.map +0 -1
  321. package/dist/notebook-creation/notebook-nav.js.map +0 -1
  322. package/dist/notebook-creation/notebook-sync.d.ts.map +0 -1
  323. package/dist/notebook-creation/notebook-sync.js.map +0 -1
  324. package/dist/notebook-creation/run-discovery.d.ts +0 -11
  325. package/dist/notebook-creation/run-discovery.d.ts.map +0 -1
  326. package/dist/notebook-creation/run-discovery.js +0 -151
  327. package/dist/notebook-creation/run-discovery.js.map +0 -1
  328. package/dist/notebook-creation/selector-discovery.d.ts +0 -65
  329. package/dist/notebook-creation/selector-discovery.d.ts.map +0 -1
  330. package/dist/notebook-creation/selector-discovery.js +0 -414
  331. package/dist/notebook-creation/selector-discovery.js.map +0 -1
  332. package/dist/notebook-creation/selectors.d.ts.map +0 -1
  333. package/dist/notebook-creation/selectors.js.map +0 -1
  334. package/dist/notebook-creation/selectors.ts +0 -112
  335. package/dist/notebook-creation/source-manager.d.ts.map +0 -1
  336. package/dist/notebook-creation/source-manager.js.map +0 -1
  337. package/dist/notebook-creation/test-create.d.ts +0 -8
  338. package/dist/notebook-creation/test-create.d.ts.map +0 -1
  339. package/dist/notebook-creation/test-create.js +0 -72
  340. package/dist/notebook-creation/test-create.js.map +0 -1
  341. package/dist/notebook-creation/types.d.ts.map +0 -1
  342. package/dist/notebook-creation/types.js.map +0 -1
  343. package/dist/notebook-creation/video-manager.d.ts.map +0 -1
  344. package/dist/notebook-creation/video-manager.js.map +0 -1
  345. package/dist/observability/metrics.d.ts.map +0 -1
  346. package/dist/observability/metrics.js.map +0 -1
  347. package/dist/quota/index.d.ts.map +0 -1
  348. package/dist/quota/index.js.map +0 -1
  349. package/dist/quota/quota-manager.d.ts.map +0 -1
  350. package/dist/quota/quota-manager.js.map +0 -1
  351. package/dist/resources/resource-handlers.d.ts.map +0 -1
  352. package/dist/resources/resource-handlers.js.map +0 -1
  353. package/dist/session/browser-session.d.ts.map +0 -1
  354. package/dist/session/browser-session.js.map +0 -1
  355. package/dist/session/session-manager.d.ts.map +0 -1
  356. package/dist/session/session-manager.js.map +0 -1
  357. package/dist/session/session-timeout.d.ts.map +0 -1
  358. package/dist/session/session-timeout.js.map +0 -1
  359. package/dist/session/shared-context-manager.d.ts.map +0 -1
  360. package/dist/session/shared-context-manager.js.map +0 -1
  361. package/dist/tools/annotations.d.ts.map +0 -1
  362. package/dist/tools/annotations.js.map +0 -1
  363. package/dist/tools/definitions/ask-question.d.ts.map +0 -1
  364. package/dist/tools/definitions/ask-question.js.map +0 -1
  365. package/dist/tools/definitions/chat-history.d.ts.map +0 -1
  366. package/dist/tools/definitions/chat-history.js.map +0 -1
  367. package/dist/tools/definitions/data-tables.d.ts.map +0 -1
  368. package/dist/tools/definitions/data-tables.js.map +0 -1
  369. package/dist/tools/definitions/gemini.d.ts.map +0 -1
  370. package/dist/tools/definitions/gemini.js.map +0 -1
  371. package/dist/tools/definitions/notebook-management.d.ts.map +0 -1
  372. package/dist/tools/definitions/notebook-management.js.map +0 -1
  373. package/dist/tools/definitions/query-history.d.ts.map +0 -1
  374. package/dist/tools/definitions/query-history.js.map +0 -1
  375. package/dist/tools/definitions/session-management.d.ts.map +0 -1
  376. package/dist/tools/definitions/session-management.js.map +0 -1
  377. package/dist/tools/definitions/system.d.ts.map +0 -1
  378. package/dist/tools/definitions/system.js.map +0 -1
  379. package/dist/tools/definitions/video.d.ts.map +0 -1
  380. package/dist/tools/definitions/video.js.map +0 -1
  381. package/dist/tools/definitions.d.ts.map +0 -1
  382. package/dist/tools/definitions.js.map +0 -1
  383. package/dist/tools/handlers/ask-question.d.ts.map +0 -1
  384. package/dist/tools/handlers/ask-question.js.map +0 -1
  385. package/dist/tools/handlers/audio-video.d.ts.map +0 -1
  386. package/dist/tools/handlers/audio-video.js.map +0 -1
  387. package/dist/tools/handlers/auth.d.ts.map +0 -1
  388. package/dist/tools/handlers/auth.js.map +0 -1
  389. package/dist/tools/handlers/error-utils.d.ts.map +0 -1
  390. package/dist/tools/handlers/error-utils.js.map +0 -1
  391. package/dist/tools/handlers/gemini.d.ts.map +0 -1
  392. package/dist/tools/handlers/gemini.js.map +0 -1
  393. package/dist/tools/handlers/index.d.ts.map +0 -1
  394. package/dist/tools/handlers/index.js.map +0 -1
  395. package/dist/tools/handlers/notebook-creation.d.ts.map +0 -1
  396. package/dist/tools/handlers/notebook-creation.js.map +0 -1
  397. package/dist/tools/handlers/notebook-management.d.ts.map +0 -1
  398. package/dist/tools/handlers/notebook-management.js.map +0 -1
  399. package/dist/tools/handlers/session-management.d.ts.map +0 -1
  400. package/dist/tools/handlers/session-management.js.map +0 -1
  401. package/dist/tools/handlers/system.d.ts.map +0 -1
  402. package/dist/tools/handlers/system.js.map +0 -1
  403. package/dist/tools/handlers/types.d.ts.map +0 -1
  404. package/dist/tools/handlers/types.js.map +0 -1
  405. package/dist/tools/handlers/webhooks.d.ts.map +0 -1
  406. package/dist/tools/handlers/webhooks.js.map +0 -1
  407. package/dist/tools/handlers.d.ts +0 -666
  408. package/dist/tools/handlers.d.ts.map +0 -1
  409. package/dist/tools/handlers.js +0 -2929
  410. package/dist/tools/handlers.js.map +0 -1
  411. package/dist/tools/icons.d.ts.map +0 -1
  412. package/dist/tools/icons.js.map +0 -1
  413. package/dist/tools/index.d.ts.map +0 -1
  414. package/dist/tools/index.js.map +0 -1
  415. package/dist/types.d.ts.map +0 -1
  416. package/dist/types.js.map +0 -1
  417. package/dist/utils/audit-logger.d.ts.map +0 -1
  418. package/dist/utils/audit-logger.js.map +0 -1
  419. package/dist/utils/cert-pinning.d.ts +0 -97
  420. package/dist/utils/cert-pinning.d.ts.map +0 -1
  421. package/dist/utils/cert-pinning.js +0 -328
  422. package/dist/utils/cert-pinning.js.map +0 -1
  423. package/dist/utils/cleanup-manager.d.ts.map +0 -1
  424. package/dist/utils/cleanup-manager.js.map +0 -1
  425. package/dist/utils/cli-handler.d.ts.map +0 -1
  426. package/dist/utils/cli-handler.js.map +0 -1
  427. package/dist/utils/crypto.d.ts.map +0 -1
  428. package/dist/utils/crypto.js.map +0 -1
  429. package/dist/utils/file-lock.d.ts.map +0 -1
  430. package/dist/utils/file-lock.js.map +0 -1
  431. package/dist/utils/file-permissions.d.ts.map +0 -1
  432. package/dist/utils/file-permissions.js.map +0 -1
  433. package/dist/utils/logger.d.ts.map +0 -1
  434. package/dist/utils/logger.js.map +0 -1
  435. package/dist/utils/page-utils.d.ts.map +0 -1
  436. package/dist/utils/page-utils.js.map +0 -1
  437. package/dist/utils/response-validator.d.ts.map +0 -1
  438. package/dist/utils/response-validator.js.map +0 -1
  439. package/dist/utils/secrets-scanner.d.ts.map +0 -1
  440. package/dist/utils/secrets-scanner.js.map +0 -1
  441. package/dist/utils/secure-memory.d.ts.map +0 -1
  442. package/dist/utils/secure-memory.js.map +0 -1
  443. package/dist/utils/security.d.ts.map +0 -1
  444. package/dist/utils/security.js.map +0 -1
  445. package/dist/utils/settings-manager.d.ts.map +0 -1
  446. package/dist/utils/settings-manager.js.map +0 -1
  447. package/dist/utils/stealth-utils.d.ts.map +0 -1
  448. package/dist/utils/stealth-utils.js.map +0 -1
  449. package/dist/utils/tool-validation.d.ts +0 -93
  450. package/dist/utils/tool-validation.d.ts.map +0 -1
  451. package/dist/utils/tool-validation.js +0 -277
  452. package/dist/utils/tool-validation.js.map +0 -1
  453. package/dist/webhooks/index.d.ts.map +0 -1
  454. package/dist/webhooks/index.js.map +0 -1
  455. package/dist/webhooks/types.d.ts.map +0 -1
  456. package/dist/webhooks/types.js.map +0 -1
  457. package/dist/webhooks/webhook-dispatcher.d.ts.map +0 -1
  458. package/dist/webhooks/webhook-dispatcher.js.map +0 -1
  459. package/docs/COMPLIANCE-SPEC.md +0 -1452
  460. package/docs/MCP-DIRECTORY-LISTINGS.md +0 -91
  461. package/docs/SECURITY-FORK-OPPORTUNITIES.md +0 -79
  462. package/docs/SECURITY_IMPLEMENTATION_PLAN.md +0 -437
  463. package/docs/archive/ISSUES-legacy-2026-04-24.md +0 -644
  464. package/docs/configuration.md +0 -94
  465. package/docs/dependency-risk.md +0 -25
  466. package/docs/improvement-sprint-2026.2.10.md +0 -210
  467. package/docs/testing-runbook.md +0 -166
  468. package/docs/tools.md +0 -34
  469. package/docs/troubleshooting.md +0 -59
  470. package/docs/usage-guide.md +0 -246
@@ -1,2929 +0,0 @@
1
- /**
2
- * MCP Tool Handlers
3
- *
4
- * Implements the logic for all MCP tools.
5
- */
6
- import { CONFIG, applyBrowserOptions } from "../config.js";
7
- import { log } from "../utils/logger.js";
8
- import { RateLimitError } from "../errors.js";
9
- import { validateNotebookUrl, validateNotebookId, validateSessionId, validateQuestion, sanitizeForLogging, RateLimiter, SecurityError, } from "../utils/security.js";
10
- import { audit } from "../utils/audit-logger.js";
11
- import { validateResponse } from "../utils/response-validator.js";
12
- import { CleanupManager } from "../utils/cleanup-manager.js";
13
- import { NotebookCreator } from "../notebook-creation/notebook-creator.js";
14
- import { NotebookSync } from "../notebook-creation/notebook-sync.js";
15
- import { SourceManager } from "../notebook-creation/source-manager.js";
16
- import { AudioManager } from "../notebook-creation/audio-manager.js";
17
- import { VideoManager } from "../notebook-creation/video-manager.js";
18
- import { DataTableManager } from "../notebook-creation/data-table-manager.js";
19
- import { getWebhookDispatcher } from "../webhooks/index.js";
20
- import { getQuotaManager } from "../quota/index.js";
21
- import { getQueryLogger } from "../logging/index.js";
22
- import { GeminiClient, } from "../gemini/index.js";
23
- const FOLLOW_UP_REMINDER = "\n\nEXTREMELY IMPORTANT: Is that ALL you need to know? You can always ask another question using the same session ID! Think about it carefully: before you reply to the user, review their original request and this answer. If anything is still unclear or missing, ask me another question first.";
24
- /**
25
- * MCP Tool Handlers
26
- */
27
- export class ToolHandlers {
28
- sessionManager;
29
- authManager;
30
- library;
31
- rateLimiter;
32
- geminiClient;
33
- constructor(sessionManager, authManager, library) {
34
- this.sessionManager = sessionManager;
35
- this.authManager = authManager;
36
- this.library = library;
37
- // Rate limit: 100 requests per minute per session (protective limit)
38
- this.rateLimiter = new RateLimiter(100, 60000);
39
- // Initialize Gemini client (may be unavailable if no API key)
40
- this.geminiClient = new GeminiClient();
41
- }
42
- /**
43
- * Handle ask_question tool
44
- */
45
- async handleAskQuestion(args, sendProgress) {
46
- const { show_browser, browser_options } = args;
47
- const startTime = Date.now();
48
- log.info(`🔧 [TOOL] ask_question called`);
49
- // === SECURITY: Input validation ===
50
- let safeQuestion;
51
- let safeSessionId;
52
- let safeNotebookId;
53
- let safeNotebookUrl;
54
- try {
55
- // Validate question (required)
56
- safeQuestion = validateQuestion(args.question);
57
- log.info(` Question: "${sanitizeForLogging(safeQuestion.substring(0, 100))}"...`);
58
- // Validate optional session_id
59
- if (args.session_id) {
60
- safeSessionId = validateSessionId(args.session_id);
61
- log.info(` Session ID: ${safeSessionId}`);
62
- }
63
- // Validate optional notebook_id
64
- if (args.notebook_id) {
65
- safeNotebookId = validateNotebookId(args.notebook_id);
66
- log.info(` Notebook ID: ${safeNotebookId}`);
67
- }
68
- // Validate optional notebook_url (CRITICAL - prevents URL injection)
69
- if (args.notebook_url) {
70
- safeNotebookUrl = validateNotebookUrl(args.notebook_url);
71
- log.info(` Notebook URL: ${safeNotebookUrl}`);
72
- }
73
- // Rate limiting check
74
- const rateLimitKey = safeSessionId || 'global';
75
- if (!this.rateLimiter.isAllowed(rateLimitKey)) {
76
- log.warning(`🚫 Rate limit exceeded for ${rateLimitKey}`);
77
- await audit.security("rate_limit_exceeded", "warning", {
78
- session_id: rateLimitKey,
79
- remaining: this.rateLimiter.getRemaining(rateLimitKey),
80
- });
81
- await audit.tool("ask_question", args, false, Date.now() - startTime, "Rate limit exceeded");
82
- return {
83
- success: false,
84
- error: `Rate limit exceeded. Please wait before making more requests. Remaining: ${this.rateLimiter.getRemaining(rateLimitKey)}`,
85
- };
86
- }
87
- // === QUOTA CHECK ===
88
- const quotaManager = getQuotaManager();
89
- const canQuery = quotaManager.canMakeQuery();
90
- if (!canQuery.allowed) {
91
- log.warning(`⚠️ Quota limit: ${canQuery.reason}`);
92
- await audit.tool("ask_question", args, false, Date.now() - startTime, canQuery.reason || "Query quota exceeded");
93
- return {
94
- success: false,
95
- error: canQuery.reason || "Daily query limit reached. Try again tomorrow or upgrade your plan.",
96
- };
97
- }
98
- }
99
- catch (error) {
100
- if (error instanceof SecurityError) {
101
- log.error(`🛡️ [SECURITY] Validation failed: ${error.message}`);
102
- await audit.security("validation_failed", "error", {
103
- tool: "ask_question",
104
- error: error.message,
105
- });
106
- await audit.tool("ask_question", args, false, Date.now() - startTime, error.message);
107
- return {
108
- success: false,
109
- error: `Security validation failed: ${error.message}`,
110
- };
111
- }
112
- throw error;
113
- }
114
- try {
115
- // Resolve notebook URL (using validated values)
116
- let resolvedNotebookUrl = safeNotebookUrl;
117
- if (!resolvedNotebookUrl && safeNotebookId) {
118
- const notebook = this.library.incrementUseCount(safeNotebookId);
119
- if (!notebook) {
120
- throw new Error(`Notebook not found in library: ${safeNotebookId}`);
121
- }
122
- resolvedNotebookUrl = notebook.url;
123
- log.info(` Resolved notebook: ${notebook.name}`);
124
- }
125
- else if (!resolvedNotebookUrl) {
126
- const active = this.library.getActiveNotebook();
127
- if (active) {
128
- const notebook = this.library.incrementUseCount(active.id);
129
- if (!notebook) {
130
- throw new Error(`Active notebook not found: ${active.id}`);
131
- }
132
- resolvedNotebookUrl = notebook.url;
133
- log.info(` Using active notebook: ${notebook.name}`);
134
- }
135
- }
136
- // Progress: Getting or creating session
137
- await sendProgress?.("Getting or creating browser session...", 1, 5);
138
- // Calculate overrideHeadless parameter for session manager
139
- // show_browser takes precedence over browser_options.headless
140
- let overrideHeadless = undefined;
141
- if (show_browser !== undefined) {
142
- overrideHeadless = show_browser;
143
- }
144
- else if (browser_options?.show !== undefined) {
145
- overrideHeadless = browser_options.show;
146
- }
147
- else if (browser_options?.headless !== undefined) {
148
- overrideHeadless = !browser_options.headless;
149
- }
150
- // Get or create session (with headless override to handle mode changes)
151
- const session = await this.sessionManager.getOrCreateSession(safeSessionId, resolvedNotebookUrl, overrideHeadless);
152
- // Progress: Asking question
153
- await sendProgress?.("Asking question to NotebookLM...", 2, 5);
154
- // Ask the question (pass progress callback) - using validated question
155
- const rawAnswer = await session.ask(safeQuestion, sendProgress);
156
- // === SECURITY: Validate response for prompt injection & malicious content ===
157
- await sendProgress?.("Validating response security...", 4, 5);
158
- const validationResult = await validateResponse(rawAnswer);
159
- // Use sanitized response if issues were found
160
- let finalAnswer;
161
- let securityWarnings = [];
162
- if (!validationResult.safe) {
163
- log.warning(`🛡️ Response contained blocked content, using sanitized version`);
164
- finalAnswer = validationResult.sanitized;
165
- securityWarnings = validationResult.blocked;
166
- }
167
- else if (validationResult.warnings.length > 0) {
168
- log.info(`⚠️ Response had ${validationResult.warnings.length} warnings`);
169
- finalAnswer = rawAnswer;
170
- securityWarnings = validationResult.warnings;
171
- }
172
- else {
173
- finalAnswer = rawAnswer;
174
- }
175
- const answer = `${finalAnswer.trimEnd()}${FOLLOW_UP_REMINDER}`;
176
- // Get session info
177
- const sessionInfo = session.getInfo();
178
- // Get quota status for response visibility
179
- const quotaStatus = getQuotaManager().getDetailedStatus();
180
- const result = {
181
- status: "success",
182
- question: safeQuestion,
183
- answer,
184
- session_id: session.sessionId,
185
- notebook_url: session.notebookUrl,
186
- session_info: {
187
- age_seconds: sessionInfo.age_seconds,
188
- message_count: sessionInfo.message_count,
189
- last_activity: sessionInfo.last_activity,
190
- },
191
- // Include quota info for visibility
192
- quota_info: {
193
- queries_remaining: quotaStatus.queries.remaining,
194
- queries_used_today: quotaStatus.queries.used,
195
- queries_limit: quotaStatus.queries.limit,
196
- should_stop: quotaStatus.queries.shouldStop,
197
- tier: quotaStatus.tier,
198
- warnings: quotaStatus.warnings,
199
- },
200
- // Include security warnings if any
201
- ...(securityWarnings.length > 0 && { security_warnings: securityWarnings }),
202
- };
203
- // Progress: Complete
204
- await sendProgress?.("Question answered successfully!", 5, 5);
205
- log.success(`✅ [TOOL] ask_question completed successfully`);
206
- // Update quota tracking (atomic for concurrent session safety)
207
- await getQuotaManager().incrementQueryCountAtomic();
208
- // Log query for research history (Phase 1)
209
- const queryLogger = getQueryLogger();
210
- const resolvedNotebook = safeNotebookId ? this.library.getNotebook(safeNotebookId) : null;
211
- await queryLogger.logQuery({
212
- sessionId: session.sessionId,
213
- notebookId: safeNotebookId,
214
- notebookUrl: session.notebookUrl,
215
- notebookName: resolvedNotebook?.name,
216
- question: safeQuestion,
217
- answer: finalAnswer,
218
- answerLength: finalAnswer.length,
219
- durationMs: Date.now() - startTime,
220
- quotaInfo: {
221
- used: quotaStatus.queries.used + 1, // +1 because we just incremented
222
- limit: quotaStatus.queries.limit,
223
- remaining: quotaStatus.queries.remaining - 1,
224
- tier: quotaStatus.tier,
225
- },
226
- });
227
- // Audit: successful tool call
228
- await audit.tool("ask_question", {
229
- question_length: safeQuestion.length,
230
- session_id: safeSessionId,
231
- notebook_id: safeNotebookId,
232
- }, true, Date.now() - startTime);
233
- return {
234
- success: true,
235
- data: result,
236
- };
237
- }
238
- catch (error) {
239
- const errorMessage = error instanceof Error ? error.message : String(error);
240
- // Special handling for rate limit errors
241
- if (error instanceof RateLimitError || errorMessage.toLowerCase().includes("rate limit")) {
242
- log.error(`🚫 [TOOL] Rate limit detected`);
243
- await audit.security("notebooklm_rate_limit", "warning", {
244
- session_id: safeSessionId,
245
- });
246
- await audit.tool("ask_question", args, false, Date.now() - startTime, "NotebookLM rate limit");
247
- return {
248
- success: false,
249
- error: "NotebookLM rate limit reached (50 queries/day for free accounts).\n\n" +
250
- "You can:\n" +
251
- "1. Use the 're_auth' tool to login with a different Google account\n" +
252
- "2. Wait until tomorrow for the quota to reset\n" +
253
- "3. Upgrade to Google AI Pro/Ultra for 5x higher limits\n\n" +
254
- `Original error: ${errorMessage}`,
255
- };
256
- }
257
- log.error(`❌ [TOOL] ask_question failed: ${errorMessage}`);
258
- await audit.tool("ask_question", args, false, Date.now() - startTime, errorMessage);
259
- return {
260
- success: false,
261
- error: errorMessage,
262
- };
263
- }
264
- }
265
- /**
266
- * Handle list_sessions tool
267
- */
268
- async handleListSessions() {
269
- log.info(`🔧 [TOOL] list_sessions called`);
270
- try {
271
- const stats = this.sessionManager.getStats();
272
- const sessions = this.sessionManager.getAllSessionsInfo();
273
- const result = {
274
- active_sessions: stats.active_sessions,
275
- max_sessions: stats.max_sessions,
276
- session_timeout: stats.session_timeout,
277
- oldest_session_seconds: stats.oldest_session_seconds,
278
- total_messages: stats.total_messages,
279
- sessions: sessions.map((info) => ({
280
- id: info.id,
281
- created_at: info.created_at,
282
- last_activity: info.last_activity,
283
- age_seconds: info.age_seconds,
284
- inactive_seconds: info.inactive_seconds,
285
- message_count: info.message_count,
286
- notebook_url: info.notebook_url,
287
- })),
288
- };
289
- log.success(`✅ [TOOL] list_sessions completed (${result.active_sessions} sessions)`);
290
- return {
291
- success: true,
292
- data: result,
293
- };
294
- }
295
- catch (error) {
296
- const errorMessage = error instanceof Error ? error.message : String(error);
297
- log.error(`❌ [TOOL] list_sessions failed: ${errorMessage}`);
298
- return {
299
- success: false,
300
- error: errorMessage,
301
- };
302
- }
303
- }
304
- /**
305
- * Handle close_session tool
306
- */
307
- async handleCloseSession(args) {
308
- const { session_id } = args;
309
- log.info(`🔧 [TOOL] close_session called`);
310
- log.info(` Session ID: ${session_id}`);
311
- try {
312
- const closed = await this.sessionManager.closeSession(session_id);
313
- if (closed) {
314
- log.success(`✅ [TOOL] close_session completed`);
315
- return {
316
- success: true,
317
- data: {
318
- status: "success",
319
- message: `Session ${session_id} closed successfully`,
320
- session_id,
321
- },
322
- };
323
- }
324
- else {
325
- log.warning(`⚠️ [TOOL] Session ${session_id} not found`);
326
- return {
327
- success: false,
328
- error: `Session ${session_id} not found`,
329
- };
330
- }
331
- }
332
- catch (error) {
333
- const errorMessage = error instanceof Error ? error.message : String(error);
334
- log.error(`❌ [TOOL] close_session failed: ${errorMessage}`);
335
- return {
336
- success: false,
337
- error: errorMessage,
338
- };
339
- }
340
- }
341
- /**
342
- * Handle reset_session tool
343
- */
344
- async handleResetSession(args) {
345
- const { session_id } = args;
346
- log.info(`🔧 [TOOL] reset_session called`);
347
- log.info(` Session ID: ${session_id}`);
348
- try {
349
- const session = this.sessionManager.getSession(session_id);
350
- if (!session) {
351
- log.warning(`⚠️ [TOOL] Session ${session_id} not found`);
352
- return {
353
- success: false,
354
- error: `Session ${session_id} not found`,
355
- };
356
- }
357
- await session.reset();
358
- log.success(`✅ [TOOL] reset_session completed`);
359
- return {
360
- success: true,
361
- data: {
362
- status: "success",
363
- message: `Session ${session_id} reset successfully`,
364
- session_id,
365
- },
366
- };
367
- }
368
- catch (error) {
369
- const errorMessage = error instanceof Error ? error.message : String(error);
370
- log.error(`❌ [TOOL] reset_session failed: ${errorMessage}`);
371
- return {
372
- success: false,
373
- error: errorMessage,
374
- };
375
- }
376
- }
377
- /**
378
- * Handle get_health tool
379
- */
380
- async handleGetHealth(args) {
381
- log.info(`🔧 [TOOL] get_health called${args?.deep_check ? ' (deep check)' : ''}`);
382
- try {
383
- // Check authentication status
384
- const statePath = await this.authManager.getValidStatePath();
385
- const authenticated = statePath !== null;
386
- // Get session stats
387
- const stats = this.sessionManager.getStats();
388
- // Deep check: actually verify the chat UI loads
389
- let chatUiAccessible;
390
- let deepCheckNotebook;
391
- if (args?.deep_check && authenticated) {
392
- log.info(` 🔍 Running deep check - verifying chat UI loads...`);
393
- try {
394
- // Find a notebook to test with
395
- let notebookUrl;
396
- if (args.notebook_id) {
397
- const notebook = this.library.getNotebook(args.notebook_id);
398
- if (notebook) {
399
- notebookUrl = notebook.url;
400
- deepCheckNotebook = notebook.name || args.notebook_id;
401
- }
402
- }
403
- if (!notebookUrl) {
404
- const activeNotebook = this.library.getActiveNotebook();
405
- if (activeNotebook) {
406
- notebookUrl = activeNotebook.url;
407
- deepCheckNotebook = activeNotebook.name || "active notebook";
408
- }
409
- }
410
- if (!notebookUrl) {
411
- // Try to get any notebook from library
412
- const notebooks = this.library.listNotebooks();
413
- if (notebooks.length > 0) {
414
- notebookUrl = notebooks[0].url;
415
- deepCheckNotebook = notebooks[0].name || "first notebook";
416
- }
417
- }
418
- if (notebookUrl) {
419
- // Create a temporary session to test
420
- const sessionId = `health-check-${Date.now()}`;
421
- const session = await this.sessionManager.getOrCreateSession(sessionId, notebookUrl);
422
- try {
423
- const page = session.getPage();
424
- if (page) {
425
- // Wait for page to load
426
- await page.waitForTimeout(3000);
427
- // Check for chat input element
428
- const chatInput = await page.$('textarea, [contenteditable="true"], .chat-input, .query-input, input[type="text"]');
429
- chatUiAccessible = chatInput !== null;
430
- if (!chatUiAccessible) {
431
- // Also check for common NotebookLM chat selectors
432
- const altSelectors = await page.$('.chat-container, .query-container, .message-input-container');
433
- chatUiAccessible = altSelectors !== null;
434
- }
435
- log.info(` 📊 Chat UI accessible: ${chatUiAccessible}`);
436
- }
437
- else {
438
- chatUiAccessible = false;
439
- }
440
- }
441
- finally {
442
- // Clean up the test session
443
- await this.sessionManager.closeSession(sessionId);
444
- }
445
- }
446
- else {
447
- log.warning(` ⚠️ No notebook available for deep check`);
448
- deepCheckNotebook = "none available";
449
- }
450
- }
451
- catch (deepCheckError) {
452
- log.warning(` ⚠️ Deep check failed: ${deepCheckError instanceof Error ? deepCheckError.message : String(deepCheckError)}`);
453
- chatUiAccessible = false;
454
- }
455
- }
456
- const result = {
457
- status: "ok",
458
- authenticated,
459
- notebook_url: CONFIG.notebookUrl || "not configured",
460
- active_sessions: stats.active_sessions,
461
- max_sessions: stats.max_sessions,
462
- session_timeout: stats.session_timeout,
463
- total_messages: stats.total_messages,
464
- headless: CONFIG.headless,
465
- auto_login_enabled: CONFIG.autoLoginEnabled,
466
- stealth_enabled: CONFIG.stealthEnabled,
467
- // Include deep check results if performed
468
- ...(args?.deep_check && {
469
- chat_ui_accessible: chatUiAccessible,
470
- deep_check_notebook: deepCheckNotebook,
471
- }),
472
- // Add troubleshooting tip if not authenticated or chat UI not accessible
473
- ...(((!authenticated) || (args?.deep_check && chatUiAccessible === false)) && {
474
- troubleshooting_tip: chatUiAccessible === false
475
- ? "Chat UI not accessible. Session may be stale. Run re_auth(show_browser:true) to refresh."
476
- : "Not authenticated. Run setup_auth(show_browser:true) to log in via a visible browser window. " +
477
- "Do NOT call cleanup_data — it does not help with auth and is not needed here."
478
- }),
479
- };
480
- log.success(`✅ [TOOL] get_health completed`);
481
- return {
482
- success: true,
483
- data: result,
484
- };
485
- }
486
- catch (error) {
487
- const errorMessage = error instanceof Error ? error.message : String(error);
488
- log.error(`❌ [TOOL] get_health failed: ${errorMessage}`);
489
- return {
490
- success: false,
491
- error: errorMessage,
492
- };
493
- }
494
- }
495
- /**
496
- * Handle setup_auth tool
497
- *
498
- * Opens a browser window for manual login with live progress updates.
499
- * The operation waits synchronously for login completion (up to 10 minutes).
500
- */
501
- async handleSetupAuth(args, sendProgress) {
502
- const { show_browser } = args;
503
- // CRITICAL: Send immediate progress to reset timeout from the very start
504
- await sendProgress?.("Initializing authentication setup...", 0, 10);
505
- log.info(`🔧 [TOOL] setup_auth called`);
506
- if (show_browser !== undefined) {
507
- log.info(` Show browser: ${show_browser}`);
508
- }
509
- // Guard: setup_auth ALWAYS clears all saved credentials before re-logging in.
510
- // Calling it headlessly will wipe auth and then fail to restore it, leaving
511
- // all sessions permanently unauthenticated. Require show_browser:true.
512
- if (!show_browser) {
513
- log.error("❌ setup_auth requires show_browser:true — cannot login interactively in headless mode");
514
- return {
515
- success: false,
516
- authenticated: false,
517
- error: "setup_auth requires show_browser:true. " +
518
- "Calling it without a visible browser wipes your saved credentials then fails to restore them. " +
519
- "Run the auth-now.mjs script instead, or pass show_browser:true.",
520
- };
521
- }
522
- const startTime = Date.now();
523
- try {
524
- // Progress: Starting
525
- await sendProgress?.("Preparing authentication browser...", 1, 10);
526
- log.info(` 🌐 Opening browser for interactive login...`);
527
- // Progress: Opening browser
528
- await sendProgress?.("Opening browser window...", 2, 10);
529
- // Perform setup with progress updates; pass show_browser so the caller
530
- // can force a visible browser even when HEADLESS=true is set in env
531
- const success = await this.authManager.performSetup(sendProgress, show_browser);
532
- const durationSeconds = (Date.now() - startTime) / 1000;
533
- if (success) {
534
- // Progress: Complete
535
- await sendProgress?.("Authentication saved successfully!", 10, 10);
536
- log.success(`✅ [TOOL] setup_auth completed (${durationSeconds.toFixed(1)}s)`);
537
- // Audit: successful authentication
538
- await audit.auth("setup_auth", true, { duration_seconds: durationSeconds });
539
- await audit.tool("setup_auth", {}, true, Date.now() - startTime);
540
- return {
541
- success: true,
542
- data: {
543
- status: "authenticated",
544
- message: "Successfully authenticated and saved browser state",
545
- authenticated: true,
546
- duration_seconds: durationSeconds,
547
- },
548
- };
549
- }
550
- else {
551
- log.error(`❌ [TOOL] setup_auth failed (${durationSeconds.toFixed(1)}s)`);
552
- // Audit: failed authentication
553
- await audit.auth("setup_auth", false, { reason: "cancelled_or_failed" });
554
- await audit.tool("setup_auth", {}, false, Date.now() - startTime, "Authentication failed or was cancelled");
555
- return {
556
- success: false,
557
- error: "Authentication failed or was cancelled",
558
- };
559
- }
560
- }
561
- catch (error) {
562
- const errorMessage = error instanceof Error ? error.message : String(error);
563
- const durationSeconds = (Date.now() - startTime) / 1000;
564
- log.error(`❌ [TOOL] setup_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
565
- // Audit: auth error
566
- await audit.auth("setup_auth", false, { error: errorMessage });
567
- await audit.tool("setup_auth", {}, false, Date.now() - startTime, errorMessage);
568
- return {
569
- success: false,
570
- error: errorMessage,
571
- };
572
- }
573
- }
574
- /**
575
- * Handle re_auth tool
576
- *
577
- * Performs a complete re-authentication:
578
- * 1. Closes all active browser sessions
579
- * 2. Deletes all saved authentication data (cookies, Chrome profile)
580
- * 3. Opens browser for fresh Google login
581
- *
582
- * Use for switching Google accounts or recovering from rate limits.
583
- */
584
- async handleReAuth(args, sendProgress) {
585
- const { show_browser } = args;
586
- await sendProgress?.("Preparing re-authentication...", 0, 12);
587
- log.info(`🔧 [TOOL] re_auth called`);
588
- if (show_browser !== undefined) {
589
- log.info(` Show browser: ${show_browser}`);
590
- }
591
- // Guard: re_auth requires a visible browser — Google login is interactive.
592
- // Calling without show_browser:true would wipe saved credentials then fail,
593
- // leaving all sessions permanently unauthenticated.
594
- if (!show_browser) {
595
- log.error("❌ re_auth requires show_browser:true — cannot login interactively in headless mode");
596
- return {
597
- success: false,
598
- error: "re_auth requires show_browser:true. Google login is interactive and cannot complete " +
599
- "in headless mode. Calling re_auth headlessly wipes your saved credentials then fails to " +
600
- "restore them, destroying auth for all concurrent sessions. Pass show_browser:true.",
601
- };
602
- }
603
- const startTime = Date.now();
604
- try {
605
- // 1. Close all active sessions
606
- await sendProgress?.("Closing all active sessions...", 1, 12);
607
- log.info(" 🛑 Closing all sessions...");
608
- await this.sessionManager.closeAllSessions();
609
- log.success(" ✅ All sessions closed");
610
- // 2. Clear all auth data
611
- await sendProgress?.("Clearing authentication data...", 2, 12);
612
- log.info(" 🗑️ Clearing all auth data...");
613
- await this.authManager.clearAllAuthData();
614
- log.success(" ✅ Auth data cleared");
615
- // 3. Perform fresh setup
616
- await sendProgress?.("Starting fresh authentication...", 3, 12);
617
- log.info(" 🌐 Starting fresh authentication setup...");
618
- const success = await this.authManager.performSetup(sendProgress);
619
- const durationSeconds = (Date.now() - startTime) / 1000;
620
- if (success) {
621
- await sendProgress?.("Re-authentication complete!", 12, 12);
622
- log.success(`✅ [TOOL] re_auth completed (${durationSeconds.toFixed(1)}s)`);
623
- // Audit: successful re-auth
624
- await audit.auth("re_auth", true, { duration_seconds: durationSeconds });
625
- await audit.tool("re_auth", {}, true, Date.now() - startTime);
626
- return {
627
- success: true,
628
- data: {
629
- status: "authenticated",
630
- message: "Successfully re-authenticated with new account. All previous sessions have been closed.",
631
- authenticated: true,
632
- duration_seconds: durationSeconds,
633
- },
634
- };
635
- }
636
- else {
637
- log.error(`❌ [TOOL] re_auth failed (${durationSeconds.toFixed(1)}s)`);
638
- // Audit: failed re-auth
639
- await audit.auth("re_auth", false, { reason: "cancelled_or_failed" });
640
- await audit.tool("re_auth", {}, false, Date.now() - startTime, "Re-authentication failed or was cancelled");
641
- return {
642
- success: false,
643
- error: "Re-authentication failed or was cancelled",
644
- };
645
- }
646
- }
647
- catch (error) {
648
- const errorMessage = error instanceof Error ? error.message : String(error);
649
- const durationSeconds = (Date.now() - startTime) / 1000;
650
- log.error(`❌ [TOOL] re_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
651
- // Audit: re-auth error
652
- await audit.auth("re_auth", false, { error: errorMessage });
653
- await audit.tool("re_auth", {}, false, Date.now() - startTime, errorMessage);
654
- return {
655
- success: false,
656
- error: errorMessage,
657
- };
658
- }
659
- }
660
- /**
661
- * Handle add_notebook tool
662
- */
663
- async handleAddNotebook(args) {
664
- log.info(`🔧 [TOOL] add_notebook called`);
665
- log.info(` Name: ${args.name}`);
666
- try {
667
- const notebook = this.library.addNotebook(args);
668
- log.success(`✅ [TOOL] add_notebook completed: ${notebook.id}`);
669
- return {
670
- success: true,
671
- data: { notebook },
672
- };
673
- }
674
- catch (error) {
675
- const errorMessage = error instanceof Error ? error.message : String(error);
676
- log.error(`❌ [TOOL] add_notebook failed: ${errorMessage}`);
677
- return {
678
- success: false,
679
- error: errorMessage,
680
- };
681
- }
682
- }
683
- /**
684
- * Handle list_notebooks tool
685
- */
686
- async handleListNotebooks() {
687
- log.info(`🔧 [TOOL] list_notebooks called`);
688
- try {
689
- const notebooks = this.library.listNotebooks();
690
- log.success(`✅ [TOOL] list_notebooks completed (${notebooks.length} notebooks)`);
691
- return {
692
- success: true,
693
- data: { notebooks },
694
- };
695
- }
696
- catch (error) {
697
- const errorMessage = error instanceof Error ? error.message : String(error);
698
- log.error(`❌ [TOOL] list_notebooks failed: ${errorMessage}`);
699
- return {
700
- success: false,
701
- error: errorMessage,
702
- };
703
- }
704
- }
705
- /**
706
- * Handle get_notebook tool
707
- */
708
- async handleGetNotebook(args) {
709
- log.info(`🔧 [TOOL] get_notebook called`);
710
- log.info(` ID: ${args.id}`);
711
- try {
712
- const notebook = this.library.getNotebook(args.id);
713
- if (!notebook) {
714
- log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
715
- return {
716
- success: false,
717
- error: `Notebook not found: ${args.id}`,
718
- };
719
- }
720
- log.success(`✅ [TOOL] get_notebook completed: ${notebook.name}`);
721
- return {
722
- success: true,
723
- data: { notebook },
724
- };
725
- }
726
- catch (error) {
727
- const errorMessage = error instanceof Error ? error.message : String(error);
728
- log.error(`❌ [TOOL] get_notebook failed: ${errorMessage}`);
729
- return {
730
- success: false,
731
- error: errorMessage,
732
- };
733
- }
734
- }
735
- /**
736
- * Handle select_notebook tool
737
- */
738
- async handleSelectNotebook(args) {
739
- log.info(`🔧 [TOOL] select_notebook called`);
740
- log.info(` ID: ${args.id}`);
741
- try {
742
- const notebook = this.library.selectNotebook(args.id);
743
- log.success(`✅ [TOOL] select_notebook completed: ${notebook.name}`);
744
- return {
745
- success: true,
746
- data: { notebook },
747
- };
748
- }
749
- catch (error) {
750
- const errorMessage = error instanceof Error ? error.message : String(error);
751
- log.error(`❌ [TOOL] select_notebook failed: ${errorMessage}`);
752
- return {
753
- success: false,
754
- error: errorMessage,
755
- };
756
- }
757
- }
758
- /**
759
- * Handle update_notebook tool
760
- */
761
- async handleUpdateNotebook(args) {
762
- log.info(`🔧 [TOOL] update_notebook called`);
763
- log.info(` ID: ${args.id}`);
764
- try {
765
- const notebook = this.library.updateNotebook(args);
766
- log.success(`✅ [TOOL] update_notebook completed: ${notebook.name}`);
767
- return {
768
- success: true,
769
- data: { notebook },
770
- };
771
- }
772
- catch (error) {
773
- const errorMessage = error instanceof Error ? error.message : String(error);
774
- log.error(`❌ [TOOL] update_notebook failed: ${errorMessage}`);
775
- return {
776
- success: false,
777
- error: errorMessage,
778
- };
779
- }
780
- }
781
- /**
782
- * Handle remove_notebook tool
783
- */
784
- async handleRemoveNotebook(args) {
785
- log.info(`🔧 [TOOL] remove_notebook called`);
786
- log.info(` ID: ${args.id}`);
787
- try {
788
- const notebook = this.library.getNotebook(args.id);
789
- if (!notebook) {
790
- log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
791
- return {
792
- success: false,
793
- error: `Notebook not found: ${args.id}`,
794
- };
795
- }
796
- const removed = this.library.removeNotebook(args.id);
797
- if (removed) {
798
- const closedSessions = await this.sessionManager.closeSessionsForNotebook(notebook.url);
799
- log.success(`✅ [TOOL] remove_notebook completed`);
800
- return {
801
- success: true,
802
- data: { removed: true, closed_sessions: closedSessions },
803
- };
804
- }
805
- else {
806
- log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
807
- return {
808
- success: false,
809
- error: `Notebook not found: ${args.id}`,
810
- };
811
- }
812
- }
813
- catch (error) {
814
- const errorMessage = error instanceof Error ? error.message : String(error);
815
- log.error(`❌ [TOOL] remove_notebook failed: ${errorMessage}`);
816
- return {
817
- success: false,
818
- error: errorMessage,
819
- };
820
- }
821
- }
822
- /**
823
- * Handle search_notebooks tool
824
- */
825
- async handleSearchNotebooks(args) {
826
- log.info(`🔧 [TOOL] search_notebooks called`);
827
- log.info(` Query: "${args.query}"`);
828
- try {
829
- const notebooks = this.library.searchNotebooks(args.query);
830
- log.success(`✅ [TOOL] search_notebooks completed (${notebooks.length} results)`);
831
- return {
832
- success: true,
833
- data: { notebooks },
834
- };
835
- }
836
- catch (error) {
837
- const errorMessage = error instanceof Error ? error.message : String(error);
838
- log.error(`❌ [TOOL] search_notebooks failed: ${errorMessage}`);
839
- return {
840
- success: false,
841
- error: errorMessage,
842
- };
843
- }
844
- }
845
- /**
846
- * Handle get_library_stats tool
847
- */
848
- async handleGetLibraryStats() {
849
- log.info(`🔧 [TOOL] get_library_stats called`);
850
- try {
851
- const stats = this.library.getStats();
852
- log.success(`✅ [TOOL] get_library_stats completed`);
853
- return {
854
- success: true,
855
- data: stats,
856
- };
857
- }
858
- catch (error) {
859
- const errorMessage = error instanceof Error ? error.message : String(error);
860
- log.error(`❌ [TOOL] get_library_stats failed: ${errorMessage}`);
861
- return {
862
- success: false,
863
- error: errorMessage,
864
- };
865
- }
866
- }
867
- /**
868
- * Handle export_library tool
869
- *
870
- * Exports notebook library to a backup file (JSON or CSV).
871
- */
872
- async handleExportLibrary(args) {
873
- const format = args.format || "json";
874
- log.info(`🔧 [TOOL] export_library called`);
875
- log.info(` Format: ${format}`);
876
- try {
877
- const notebooks = this.library.listNotebooks();
878
- const stats = this.library.getStats();
879
- // Generate default output path if not provided
880
- const date = new Date().toISOString().split("T")[0];
881
- const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
882
- const defaultPath = `${homeDir}/notebooklm-library-backup-${date}.${format}`;
883
- const outputPath = args.output_path || defaultPath;
884
- let content;
885
- if (format === "csv") {
886
- // CSV format: name, url, topics, last_used, use_count
887
- const headers = ["name", "url", "topics", "description", "last_used", "use_count"];
888
- const rows = notebooks.map((nb) => [
889
- `"${(nb.name || "").replace(/"/g, '""')}"`,
890
- `"${nb.url}"`,
891
- `"${(nb.topics || []).join("; ")}"`,
892
- `"${(nb.description || "").replace(/"/g, '""')}"`,
893
- nb.last_used || "",
894
- String(nb.use_count || 0),
895
- ]);
896
- content = [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
897
- }
898
- else {
899
- // JSON format: full library data
900
- content = JSON.stringify({
901
- exported_at: new Date().toISOString(),
902
- version: "1.0",
903
- stats: {
904
- total_notebooks: stats.total_notebooks,
905
- total_queries: stats.total_queries,
906
- },
907
- notebooks: notebooks,
908
- }, null, 2);
909
- }
910
- // Write file with secure permissions
911
- const fs = await import("fs");
912
- fs.writeFileSync(outputPath, content, { mode: 0o600 });
913
- const fileStats = fs.statSync(outputPath);
914
- log.success(`✅ [TOOL] export_library completed: ${outputPath}`);
915
- return {
916
- success: true,
917
- data: {
918
- file_path: outputPath,
919
- format,
920
- notebook_count: notebooks.length,
921
- size_bytes: fileStats.size,
922
- },
923
- };
924
- }
925
- catch (error) {
926
- const errorMessage = error instanceof Error ? error.message : String(error);
927
- log.error(`❌ [TOOL] export_library failed: ${errorMessage}`);
928
- return {
929
- success: false,
930
- error: errorMessage,
931
- };
932
- }
933
- }
934
- /**
935
- * Handle get_project_info tool
936
- *
937
- * Returns current project context and library location.
938
- */
939
- async handleGetProjectInfo() {
940
- log.info(`🔧 [TOOL] get_project_info called`);
941
- try {
942
- // Get info from the library instance
943
- const projectInfo = this.library.getProjectInfo();
944
- const libraryPath = this.library.getLibraryPath();
945
- const isProjectLibrary = this.library.isProjectLibrary();
946
- // Also detect what project would be detected from cwd
947
- const { NotebookLibrary: NL } = await import("../library/notebook-library.js");
948
- const detectedProject = NL.detectCurrentProject();
949
- log.success(`✅ [TOOL] get_project_info completed`);
950
- return {
951
- success: true,
952
- data: {
953
- project: projectInfo,
954
- library_path: libraryPath,
955
- is_project_library: isProjectLibrary,
956
- detected_project: detectedProject,
957
- },
958
- };
959
- }
960
- catch (error) {
961
- const errorMessage = error instanceof Error ? error.message : String(error);
962
- log.error(`❌ [TOOL] get_project_info failed: ${errorMessage}`);
963
- return {
964
- success: false,
965
- error: errorMessage,
966
- };
967
- }
968
- }
969
- /**
970
- * Handle get_quota tool
971
- *
972
- * Returns current quota status including license tier, usage, and limits.
973
- * If sync=true, navigates to NotebookLM to fetch actual quota from Google.
974
- */
975
- async handleGetQuota(args = {}) {
976
- const { sync = false } = args;
977
- log.info(`🔧 [TOOL] get_quota called (sync=${sync})`);
978
- try {
979
- const quotaManager = getQuotaManager();
980
- let syncedFromGoogle = false;
981
- let googleQuota = null;
982
- let rateLimitDetected = false;
983
- // If sync requested, navigate to NotebookLM and scrape quota
984
- if (sync) {
985
- log.info("📊 Syncing quota from Google NotebookLM...");
986
- try {
987
- // Get the shared context manager from session manager
988
- const contextManager = this.sessionManager.getContextManager();
989
- const context = await contextManager.getOrCreateContext();
990
- // Create a new page to check quota
991
- const page = await context.newPage();
992
- try {
993
- // Navigate to NotebookLM homepage
994
- await page.goto("https://notebooklm.google.com/", {
995
- waitUntil: "networkidle",
996
- timeout: 30000,
997
- });
998
- // Wait for page to load
999
- await page.waitForTimeout(2000);
1000
- // Update quota from UI
1001
- const syncResult = await quotaManager.updateFromUI(page);
1002
- syncedFromGoogle = true;
1003
- googleQuota = syncResult.queryUsageFromGoogle;
1004
- rateLimitDetected = syncResult.rateLimitDetected;
1005
- log.success(`✅ Synced quota from Google: ${googleQuota ? `${googleQuota.used}/${googleQuota.limit}` : "usage not displayed in UI"}`);
1006
- }
1007
- finally {
1008
- await page.close();
1009
- }
1010
- }
1011
- catch (syncError) {
1012
- const syncErrorMsg = syncError instanceof Error ? syncError.message : String(syncError);
1013
- log.warning(`⚠️ Could not sync from Google: ${syncErrorMsg}. Using local tracking.`);
1014
- }
1015
- }
1016
- const detailedStatus = quotaManager.getDetailedStatus();
1017
- const settings = quotaManager.getSettings();
1018
- log.success(`✅ [TOOL] get_quota completed (tier: ${detailedStatus.tier}, ${detailedStatus.queries.remaining} queries remaining, synced=${syncedFromGoogle})`);
1019
- return {
1020
- success: true,
1021
- data: {
1022
- tier: detailedStatus.tier,
1023
- notebooks: {
1024
- used: detailedStatus.notebooks.used,
1025
- limit: detailedStatus.notebooks.limit,
1026
- remaining: detailedStatus.notebooks.remaining,
1027
- percent: detailedStatus.notebooks.percentUsed,
1028
- },
1029
- sources: detailedStatus.sources,
1030
- queries: {
1031
- used: detailedStatus.queries.used,
1032
- limit: detailedStatus.queries.limit,
1033
- remaining: detailedStatus.queries.remaining,
1034
- percent: detailedStatus.queries.percentUsed,
1035
- should_stop: detailedStatus.queries.shouldStop,
1036
- reset_time: detailedStatus.queries.resetTime,
1037
- },
1038
- warnings: detailedStatus.warnings,
1039
- auto_detected: settings.autoDetected,
1040
- last_updated: settings.usage.lastUpdated,
1041
- synced_from_google: syncedFromGoogle,
1042
- google_quota: googleQuota,
1043
- rate_limit_detected: rateLimitDetected,
1044
- },
1045
- };
1046
- }
1047
- catch (error) {
1048
- const errorMessage = error instanceof Error ? error.message : String(error);
1049
- log.error(`❌ [TOOL] get_quota failed: ${errorMessage}`);
1050
- return {
1051
- success: false,
1052
- error: errorMessage,
1053
- };
1054
- }
1055
- }
1056
- /**
1057
- * Handle set_quota_tier tool
1058
- *
1059
- * Manually set the license tier to override auto-detection.
1060
- */
1061
- async handleSetQuotaTier(args) {
1062
- log.info(`🔧 [TOOL] set_quota_tier called`);
1063
- log.info(` Tier: ${args.tier}`);
1064
- try {
1065
- const quotaManager = getQuotaManager();
1066
- quotaManager.setTier(args.tier);
1067
- const settings = quotaManager.getSettings();
1068
- log.success(`✅ [TOOL] set_quota_tier completed (tier: ${args.tier})`);
1069
- return {
1070
- success: true,
1071
- data: {
1072
- tier: settings.tier,
1073
- limits: {
1074
- notebooks: settings.limits.notebooks,
1075
- sourcesPerNotebook: settings.limits.sourcesPerNotebook,
1076
- queriesPerDay: settings.limits.queriesPerDay,
1077
- },
1078
- message: `License tier set to ${args.tier}. Limits updated accordingly.`,
1079
- },
1080
- };
1081
- }
1082
- catch (error) {
1083
- const errorMessage = error instanceof Error ? error.message : String(error);
1084
- log.error(`❌ [TOOL] set_quota_tier failed: ${errorMessage}`);
1085
- return {
1086
- success: false,
1087
- error: errorMessage,
1088
- };
1089
- }
1090
- }
1091
- /**
1092
- * Handle create_notebook tool
1093
- *
1094
- * Creates a new NotebookLM notebook with sources programmatically.
1095
- */
1096
- async handleCreateNotebook(args, sendProgress) {
1097
- log.info(`🔧 [TOOL] create_notebook called`);
1098
- log.info(` Name: ${args.name}`);
1099
- log.info(` Sources: ${args.sources?.length || 0}`);
1100
- try {
1101
- // Validate inputs
1102
- if (!args.name || typeof args.name !== "string") {
1103
- throw new Error("Notebook name is required");
1104
- }
1105
- if (!args.sources || !Array.isArray(args.sources) || args.sources.length === 0) {
1106
- throw new Error("At least one source is required");
1107
- }
1108
- // Validate each source
1109
- for (const source of args.sources) {
1110
- if (!source.type || !["url", "text", "file"].includes(source.type)) {
1111
- throw new Error(`Invalid source type: ${source.type}. Must be url, text, or file.`);
1112
- }
1113
- if (!source.value || typeof source.value !== "string") {
1114
- throw new Error("Source value is required");
1115
- }
1116
- if (source.type === "url") {
1117
- try {
1118
- new URL(source.value);
1119
- }
1120
- catch {
1121
- throw new Error(`Invalid URL: ${source.value}`);
1122
- }
1123
- }
1124
- }
1125
- // === QUOTA CHECK ===
1126
- const quotaManager = getQuotaManager();
1127
- const canCreate = quotaManager.canCreateNotebook();
1128
- if (!canCreate.allowed) {
1129
- log.warning(`⚠️ Quota limit: ${canCreate.reason}`);
1130
- return {
1131
- success: false,
1132
- error: canCreate.reason || "Notebook quota limit reached",
1133
- };
1134
- }
1135
- // Check source limit
1136
- const sourceLimits = quotaManager.getLimits();
1137
- if (args.sources.length > sourceLimits.sourcesPerNotebook) {
1138
- const reason = `Too many sources (${args.sources.length}). Limit is ${sourceLimits.sourcesPerNotebook} per notebook.`;
1139
- log.warning(`⚠️ Quota limit: ${reason}`);
1140
- return {
1141
- success: false,
1142
- error: reason,
1143
- };
1144
- }
1145
- // Get the shared context manager from session manager
1146
- const contextManager = this.sessionManager.getContextManager();
1147
- // Create notebook
1148
- const creator = new NotebookCreator(this.authManager, contextManager);
1149
- const result = await creator.createNotebook({
1150
- name: args.name,
1151
- sources: args.sources,
1152
- sendProgress,
1153
- browserOptions: args.browser_options || (args.show_browser ? { show: true } : undefined),
1154
- });
1155
- // Auto-add to library if requested (default: true)
1156
- if (args.auto_add_to_library !== false) {
1157
- try {
1158
- this.library.addNotebook({
1159
- url: result.url,
1160
- name: args.name,
1161
- description: args.description || `Created ${new Date().toLocaleDateString()}`,
1162
- topics: args.topics || [],
1163
- });
1164
- log.success(`✅ Added notebook to library: ${args.name}`);
1165
- }
1166
- catch (libError) {
1167
- log.warning(`⚠️ Failed to add to library: ${libError}`);
1168
- // Don't fail the whole operation
1169
- }
1170
- }
1171
- // Update quota tracking
1172
- quotaManager.incrementNotebookCount();
1173
- // Audit log
1174
- await audit.tool("create_notebook", {
1175
- name: args.name,
1176
- sourceCount: args.sources.length,
1177
- url: result.url,
1178
- }, true, 0);
1179
- log.success(`✅ [TOOL] create_notebook completed: ${result.url}`);
1180
- return {
1181
- success: true,
1182
- data: result,
1183
- };
1184
- }
1185
- catch (error) {
1186
- const errorMessage = error instanceof Error ? error.message : String(error);
1187
- log.error(`❌ [TOOL] create_notebook failed: ${errorMessage}`);
1188
- await audit.tool("create_notebook", {
1189
- name: args.name,
1190
- error: errorMessage,
1191
- }, false, 0, errorMessage);
1192
- return {
1193
- success: false,
1194
- error: errorMessage,
1195
- };
1196
- }
1197
- }
1198
- /**
1199
- * Handle batch_create_notebooks tool
1200
- *
1201
- * Creates multiple notebooks in a single batch operation.
1202
- */
1203
- async handleBatchCreateNotebooks(args, sendProgress) {
1204
- log.info(`🔧 [TOOL] batch_create_notebooks called`);
1205
- log.info(` Notebooks: ${args.notebooks.length}`);
1206
- log.info(` Stop on error: ${args.stop_on_error || false}`);
1207
- try {
1208
- // Validate input
1209
- if (!args.notebooks || !Array.isArray(args.notebooks)) {
1210
- throw new Error("notebooks array is required");
1211
- }
1212
- if (args.notebooks.length === 0) {
1213
- throw new Error("At least one notebook is required");
1214
- }
1215
- if (args.notebooks.length > 10) {
1216
- throw new Error("Maximum 10 notebooks per batch");
1217
- }
1218
- const results = [];
1219
- const total = args.notebooks.length;
1220
- let succeeded = 0;
1221
- let failed = 0;
1222
- for (let i = 0; i < args.notebooks.length; i++) {
1223
- const notebook = args.notebooks[i];
1224
- await sendProgress?.(`Creating notebook ${i + 1}/${total}: ${notebook.name}`, i, total);
1225
- log.info(` 📓 Creating notebook ${i + 1}/${total}: ${notebook.name}`);
1226
- try {
1227
- const result = await this.handleCreateNotebook({
1228
- name: notebook.name,
1229
- sources: notebook.sources,
1230
- description: notebook.description,
1231
- topics: notebook.topics,
1232
- auto_add_to_library: true,
1233
- show_browser: args.show_browser,
1234
- });
1235
- if (result.success && result.data) {
1236
- results.push({
1237
- name: notebook.name,
1238
- success: true,
1239
- url: result.data.url,
1240
- });
1241
- succeeded++;
1242
- log.success(` ✅ Created: ${result.data.url}`);
1243
- }
1244
- else {
1245
- results.push({
1246
- name: notebook.name,
1247
- success: false,
1248
- error: result.error || "Unknown error",
1249
- });
1250
- failed++;
1251
- log.error(` ❌ Failed: ${result.error}`);
1252
- if (args.stop_on_error) {
1253
- log.warning(` ⚠️ Stopping batch due to error (stop_on_error=true)`);
1254
- break;
1255
- }
1256
- }
1257
- }
1258
- catch (error) {
1259
- const errorMessage = error instanceof Error ? error.message : String(error);
1260
- results.push({
1261
- name: notebook.name,
1262
- success: false,
1263
- error: errorMessage,
1264
- });
1265
- failed++;
1266
- log.error(` ❌ Exception: ${errorMessage}`);
1267
- if (args.stop_on_error) {
1268
- log.warning(` ⚠️ Stopping batch due to exception (stop_on_error=true)`);
1269
- break;
1270
- }
1271
- }
1272
- // Delay between notebooks to avoid rate limiting
1273
- if (i < args.notebooks.length - 1) {
1274
- const delay = 2000 + Math.random() * 2000; // 2-4 seconds
1275
- await new Promise((resolve) => setTimeout(resolve, delay));
1276
- }
1277
- }
1278
- await sendProgress?.(`Batch complete: ${succeeded}/${total} succeeded`, total, total);
1279
- log.success(`✅ [TOOL] batch_create_notebooks completed: ${succeeded}/${total} succeeded`);
1280
- return {
1281
- success: failed === 0,
1282
- data: {
1283
- total,
1284
- succeeded,
1285
- failed,
1286
- results,
1287
- },
1288
- };
1289
- }
1290
- catch (error) {
1291
- const errorMessage = error instanceof Error ? error.message : String(error);
1292
- log.error(`❌ [TOOL] batch_create_notebooks failed: ${errorMessage}`);
1293
- return {
1294
- success: false,
1295
- error: errorMessage,
1296
- };
1297
- }
1298
- }
1299
- /**
1300
- * Handle sync_library tool
1301
- *
1302
- * Syncs local library with actual NotebookLM notebooks.
1303
- */
1304
- async handleSyncLibrary(args) {
1305
- log.info(`🔧 [TOOL] sync_library called`);
1306
- log.info(` Auto-fix: ${args.auto_fix || false}`);
1307
- log.info(` Show browser: ${args.show_browser || false}`);
1308
- try {
1309
- // Get the shared context manager from session manager
1310
- const contextManager = this.sessionManager.getContextManager();
1311
- // Sync library
1312
- const sync = new NotebookSync(this.authManager, contextManager, this.library);
1313
- const result = await sync.syncLibrary({
1314
- autoFix: args.auto_fix,
1315
- showBrowser: args.show_browser,
1316
- });
1317
- // Audit log
1318
- await audit.tool("sync_library", {
1319
- matched: result.matched.length,
1320
- stale: result.staleEntries.length,
1321
- missing: result.missingNotebooks.length,
1322
- autoFix: args.auto_fix,
1323
- }, true, 0);
1324
- log.success(`✅ [TOOL] sync_library completed`);
1325
- return {
1326
- success: true,
1327
- data: result,
1328
- };
1329
- }
1330
- catch (error) {
1331
- const errorMessage = error instanceof Error ? error.message : String(error);
1332
- log.error(`❌ [TOOL] sync_library failed: ${errorMessage}`);
1333
- await audit.tool("sync_library", {
1334
- error: errorMessage,
1335
- }, false, 0, errorMessage);
1336
- return {
1337
- success: false,
1338
- error: errorMessage,
1339
- };
1340
- }
1341
- }
1342
- /**
1343
- * Handle cleanup_data tool
1344
- *
1345
- * ULTRATHINK Deep Cleanup - scans entire system for ALL NotebookLM MCP files
1346
- */
1347
- async handleCleanupData(args) {
1348
- const { confirm, preserve_library = false } = args;
1349
- log.info(`🔧 [TOOL] cleanup_data called`);
1350
- log.info(` Confirm: ${confirm}`);
1351
- log.info(` Preserve Library: ${preserve_library}`);
1352
- const cleanupManager = new CleanupManager();
1353
- try {
1354
- // Always run in deep mode
1355
- const mode = "deep";
1356
- if (!confirm) {
1357
- // Preview mode - show what would be deleted
1358
- log.info(` 📋 Generating cleanup preview (mode: ${mode})...`);
1359
- const preview = await cleanupManager.getCleanupPaths(mode, preserve_library);
1360
- const platformInfo = cleanupManager.getPlatformInfo();
1361
- log.info(` Found ${preview.totalPaths.length} items (${cleanupManager.formatBytes(preview.totalSizeBytes)})`);
1362
- log.info(` Platform: ${platformInfo.platform}`);
1363
- return {
1364
- success: true,
1365
- data: {
1366
- status: "preview",
1367
- mode,
1368
- preview: {
1369
- categories: preview.categories,
1370
- totalPaths: preview.totalPaths.length,
1371
- totalSizeBytes: preview.totalSizeBytes,
1372
- },
1373
- },
1374
- };
1375
- }
1376
- else {
1377
- // Cleanup mode - actually delete files
1378
- log.info(` 🗑️ Performing cleanup (mode: ${mode})...`);
1379
- const result = await cleanupManager.performCleanup(mode, preserve_library);
1380
- if (result.success) {
1381
- log.success(`✅ [TOOL] cleanup_data completed - deleted ${result.deletedPaths.length} items`);
1382
- }
1383
- else {
1384
- log.warning(`⚠️ [TOOL] cleanup_data completed with ${result.failedPaths.length} errors`);
1385
- }
1386
- return {
1387
- success: result.success,
1388
- data: {
1389
- status: result.success ? "completed" : "partial",
1390
- mode,
1391
- result: {
1392
- deletedPaths: result.deletedPaths,
1393
- failedPaths: result.failedPaths,
1394
- totalSizeBytes: result.totalSizeBytes,
1395
- categorySummary: result.categorySummary,
1396
- },
1397
- },
1398
- };
1399
- }
1400
- }
1401
- catch (error) {
1402
- const errorMessage = error instanceof Error ? error.message : String(error);
1403
- log.error(`❌ [TOOL] cleanup_data failed: ${errorMessage}`);
1404
- return {
1405
- success: false,
1406
- error: errorMessage,
1407
- };
1408
- }
1409
- }
1410
- /**
1411
- * Handle list_sources tool
1412
- *
1413
- * List all sources in a NotebookLM notebook.
1414
- */
1415
- async handleListSources(args) {
1416
- log.info(`🔧 [TOOL] list_sources called`);
1417
- try {
1418
- // Resolve notebook URL
1419
- let notebookUrl = args.notebook_url;
1420
- if (!notebookUrl && args.notebook_id) {
1421
- const notebook = this.library.getNotebook(args.notebook_id);
1422
- if (!notebook) {
1423
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1424
- }
1425
- notebookUrl = notebook.url;
1426
- log.info(` Resolved notebook: ${notebook.name}`);
1427
- }
1428
- else if (!notebookUrl) {
1429
- const active = this.library.getActiveNotebook();
1430
- if (active) {
1431
- notebookUrl = active.url;
1432
- log.info(` Using active notebook: ${active.name}`);
1433
- }
1434
- else {
1435
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1436
- }
1437
- }
1438
- // Validate URL
1439
- const safeUrl = validateNotebookUrl(notebookUrl);
1440
- // Get the shared context manager from session manager
1441
- const contextManager = this.sessionManager.getContextManager();
1442
- // List sources
1443
- const sourceManager = new SourceManager(this.authManager, contextManager);
1444
- const result = await sourceManager.listSources(safeUrl);
1445
- log.success(`✅ [TOOL] list_sources completed (${result.count} sources)`);
1446
- return {
1447
- success: true,
1448
- data: result,
1449
- };
1450
- }
1451
- catch (error) {
1452
- const errorMessage = error instanceof Error ? error.message : String(error);
1453
- log.error(`❌ [TOOL] list_sources failed: ${errorMessage}`);
1454
- return {
1455
- success: false,
1456
- error: errorMessage,
1457
- };
1458
- }
1459
- }
1460
- /**
1461
- * Handle add_source tool
1462
- *
1463
- * Add a source to an existing NotebookLM notebook.
1464
- */
1465
- async handleAddSource(args) {
1466
- log.info(`🔧 [TOOL] add_source called`);
1467
- log.info(` Source type: ${args.source?.type}`);
1468
- try {
1469
- // Validate source
1470
- if (!args.source || !args.source.type || !args.source.value) {
1471
- throw new Error("Source with type and value is required");
1472
- }
1473
- if (!["url", "text", "file"].includes(args.source.type)) {
1474
- throw new Error(`Invalid source type: ${args.source.type}. Must be url, text, or file.`);
1475
- }
1476
- // Resolve notebook URL
1477
- let notebookUrl = args.notebook_url;
1478
- if (!notebookUrl && args.notebook_id) {
1479
- const notebook = this.library.getNotebook(args.notebook_id);
1480
- if (!notebook) {
1481
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1482
- }
1483
- notebookUrl = notebook.url;
1484
- log.info(` Resolved notebook: ${notebook.name}`);
1485
- }
1486
- else if (!notebookUrl) {
1487
- const active = this.library.getActiveNotebook();
1488
- if (active) {
1489
- notebookUrl = active.url;
1490
- log.info(` Using active notebook: ${active.name}`);
1491
- }
1492
- else {
1493
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1494
- }
1495
- }
1496
- // Validate URL
1497
- const safeUrl = validateNotebookUrl(notebookUrl);
1498
- // Get the shared context manager from session manager
1499
- const contextManager = this.sessionManager.getContextManager();
1500
- // Add source
1501
- const sourceManager = new SourceManager(this.authManager, contextManager);
1502
- const result = await sourceManager.addSource(safeUrl, args.source);
1503
- if (result.success) {
1504
- log.success(`✅ [TOOL] add_source completed`);
1505
- }
1506
- else {
1507
- log.warning(`⚠️ [TOOL] add_source failed: ${result.error}`);
1508
- }
1509
- return {
1510
- success: result.success,
1511
- data: result,
1512
- ...(result.error && { error: result.error }),
1513
- };
1514
- }
1515
- catch (error) {
1516
- const errorMessage = error instanceof Error ? error.message : String(error);
1517
- log.error(`❌ [TOOL] add_source failed: ${errorMessage}`);
1518
- return {
1519
- success: false,
1520
- error: errorMessage,
1521
- };
1522
- }
1523
- }
1524
- /**
1525
- * Handle add_folder tool
1526
- *
1527
- * Scans a local directory for supported files and adds them all as sources
1528
- * to a NotebookLM notebook. Auto-splits into multiple notebooks if the file
1529
- * count exceeds the tier's sourcesPerNotebook limit.
1530
- */
1531
- async handleAddFolder(args, sendProgress) {
1532
- const { promises: fs } = await import("fs");
1533
- const path = await import("path");
1534
- const folderPath = args.folder_path;
1535
- const recursive = args.recursive ?? false;
1536
- const dryRun = args.dry_run ?? false;
1537
- const fileTypes = (args.file_types ?? [".pdf", ".txt", ".md", ".docx"]).map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
1538
- log.info(`🔧 [TOOL] add_folder called`);
1539
- log.info(` Folder: ${folderPath}`);
1540
- log.info(` File types: ${fileTypes.join(", ")}`);
1541
- log.info(` Recursive: ${recursive}`);
1542
- log.info(` Dry run: ${dryRun}`);
1543
- try {
1544
- // ── 1. Validate folder ───────────────────────────────────────────────
1545
- let stat;
1546
- try {
1547
- stat = await fs.stat(folderPath);
1548
- }
1549
- catch {
1550
- throw new Error(`Folder not found: ${folderPath}`);
1551
- }
1552
- if (!stat.isDirectory()) {
1553
- throw new Error(`Path is not a directory: ${folderPath}`);
1554
- }
1555
- // ── 2. Scan files ────────────────────────────────────────────────────
1556
- const scanDir = async (dir) => {
1557
- const entries = await fs.readdir(dir, { withFileTypes: true });
1558
- const files = [];
1559
- for (const entry of entries) {
1560
- const fullPath = path.join(dir, entry.name);
1561
- if (entry.isDirectory() && recursive) {
1562
- files.push(...(await scanDir(fullPath)));
1563
- }
1564
- else if (entry.isFile()) {
1565
- const ext = path.extname(entry.name).toLowerCase();
1566
- if (fileTypes.includes(ext)) {
1567
- files.push(fullPath);
1568
- }
1569
- }
1570
- }
1571
- return files.sort();
1572
- };
1573
- await sendProgress?.("Scanning folder...", 0, 10);
1574
- const allFiles = await scanDir(folderPath);
1575
- if (allFiles.length === 0) {
1576
- return {
1577
- success: true,
1578
- data: {
1579
- files_found: 0,
1580
- files_added: 0,
1581
- files_failed: 0,
1582
- files_skipped: 0,
1583
- notebooks_used: [],
1584
- failed_files: [],
1585
- dry_run: dryRun,
1586
- },
1587
- error: `No supported files found in ${folderPath} (looking for: ${fileTypes.join(", ")})`,
1588
- };
1589
- }
1590
- log.info(` Found ${allFiles.length} files`);
1591
- await sendProgress?.(`Found ${allFiles.length} files`, 1, 10);
1592
- // ── 3. Dry run — return preview ──────────────────────────────────────
1593
- if (dryRun) {
1594
- return {
1595
- success: true,
1596
- data: {
1597
- files_found: allFiles.length,
1598
- files_added: 0,
1599
- files_failed: 0,
1600
- files_skipped: 0,
1601
- notebooks_used: [],
1602
- failed_files: [],
1603
- dry_run: true,
1604
- },
1605
- };
1606
- }
1607
- // ── 4. Resolve target notebook ───────────────────────────────────────
1608
- let notebookUrl = args.notebook_url;
1609
- let notebookName = args.notebook_name_prefix ?? path.basename(folderPath);
1610
- if (!notebookUrl && args.notebook_id) {
1611
- const notebook = this.library.getNotebook(args.notebook_id);
1612
- if (!notebook)
1613
- throw new Error(`Notebook not found: ${args.notebook_id}`);
1614
- notebookUrl = notebook.url;
1615
- notebookName = notebook.name;
1616
- log.info(` Target notebook: ${notebookName}`);
1617
- }
1618
- else if (!notebookUrl) {
1619
- const active = this.library.getActiveNotebook();
1620
- if (active) {
1621
- notebookUrl = active.url;
1622
- notebookName = active.name;
1623
- log.info(` Using active notebook: ${notebookName}`);
1624
- }
1625
- else {
1626
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1627
- }
1628
- }
1629
- // ── 5. Check tier limit and chunk files ──────────────────────────────
1630
- const limits = getQuotaManager().getLimits();
1631
- const chunkSize = limits.sourcesPerNotebook;
1632
- const chunks = [];
1633
- for (let i = 0; i < allFiles.length; i += chunkSize) {
1634
- chunks.push(allFiles.slice(i, i + chunkSize));
1635
- }
1636
- log.info(` Tier limit: ${chunkSize} sources/notebook → ${chunks.length} chunk(s)`);
1637
- // ── 6. Add files ─────────────────────────────────────────────────────
1638
- const contextManager = this.sessionManager.getContextManager();
1639
- const notebooksUsed = [];
1640
- const failedFiles = [];
1641
- let totalAdded = 0;
1642
- for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
1643
- const chunk = chunks[chunkIdx];
1644
- // Determine notebook URL for this chunk
1645
- let targetUrl;
1646
- if (chunkIdx === 0) {
1647
- targetUrl = validateNotebookUrl(notebookUrl);
1648
- }
1649
- else {
1650
- // Auto-create overflow notebook
1651
- const overflowName = chunks.length === 2
1652
- ? `${notebookName} (2/2)`
1653
- : `${notebookName} (${chunkIdx + 1}/${chunks.length})`;
1654
- await sendProgress?.(`Creating overflow notebook: ${overflowName}`, 2, 10);
1655
- log.info(` Creating overflow notebook: ${overflowName}`);
1656
- const created = await this.handleCreateNotebook({ name: overflowName, sources: [], auto_add_to_library: true }, sendProgress);
1657
- if (!created.success || !created.data?.url) {
1658
- failedFiles.push(...chunk.map((f) => ({ file: f, error: `Could not create overflow notebook ${overflowName}` })));
1659
- continue;
1660
- }
1661
- targetUrl = validateNotebookUrl(created.data.url);
1662
- notebooksUsed.push(overflowName);
1663
- }
1664
- if (chunkIdx === 0) {
1665
- notebooksUsed.unshift(notebookName);
1666
- }
1667
- const sourceManager = new SourceManager(this.authManager, contextManager);
1668
- for (let i = 0; i < chunk.length; i++) {
1669
- const filePath = chunk[i];
1670
- const fileName = path.basename(filePath);
1671
- const globalIdx = chunkIdx * chunkSize + i + 1;
1672
- const progressStep = Math.min(9, 2 + Math.floor((globalIdx / allFiles.length) * 7));
1673
- await sendProgress?.(`Adding file ${globalIdx}/${allFiles.length}: ${fileName}`, progressStep, 10);
1674
- log.info(` [${globalIdx}/${allFiles.length}] Adding: ${fileName}`);
1675
- try {
1676
- const result = await sourceManager.addSource(targetUrl, {
1677
- type: "file",
1678
- value: filePath,
1679
- });
1680
- if (result.success) {
1681
- totalAdded++;
1682
- }
1683
- else {
1684
- failedFiles.push({ file: filePath, error: result.error ?? "Unknown error" });
1685
- log.warning(` ⚠️ Failed: ${fileName} — ${result.error}`);
1686
- }
1687
- }
1688
- catch (err) {
1689
- const msg = err instanceof Error ? err.message : String(err);
1690
- failedFiles.push({ file: filePath, error: msg });
1691
- log.warning(` ⚠️ Error: ${fileName} — ${msg}`);
1692
- }
1693
- }
1694
- }
1695
- await sendProgress?.("Done!", 10, 10);
1696
- log.success(`✅ [TOOL] add_folder complete: ${totalAdded}/${allFiles.length} added, ${failedFiles.length} failed`);
1697
- return {
1698
- success: failedFiles.length === 0,
1699
- data: {
1700
- files_found: allFiles.length,
1701
- files_added: totalAdded,
1702
- files_failed: failedFiles.length,
1703
- files_skipped: allFiles.length - totalAdded - failedFiles.length,
1704
- notebooks_used: notebooksUsed,
1705
- failed_files: failedFiles,
1706
- dry_run: false,
1707
- },
1708
- ...(failedFiles.length > 0 && {
1709
- error: `${failedFiles.length} file(s) failed to add`,
1710
- }),
1711
- };
1712
- }
1713
- catch (error) {
1714
- const errorMessage = error instanceof Error ? error.message : String(error);
1715
- log.error(`❌ [TOOL] add_folder failed: ${errorMessage}`);
1716
- return { success: false, error: errorMessage };
1717
- }
1718
- }
1719
- /**
1720
- * Handle remove_source tool
1721
- *
1722
- * Remove a source from a NotebookLM notebook.
1723
- */
1724
- async handleRemoveSource(args) {
1725
- log.info(`🔧 [TOOL] remove_source called`);
1726
- log.info(` Source ID: ${args.source_id}`);
1727
- try {
1728
- // Validate source_id
1729
- if (!args.source_id) {
1730
- throw new Error("source_id is required");
1731
- }
1732
- // Resolve notebook URL
1733
- let notebookUrl = args.notebook_url;
1734
- if (!notebookUrl && args.notebook_id) {
1735
- const notebook = this.library.getNotebook(args.notebook_id);
1736
- if (!notebook) {
1737
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1738
- }
1739
- notebookUrl = notebook.url;
1740
- log.info(` Resolved notebook: ${notebook.name}`);
1741
- }
1742
- else if (!notebookUrl) {
1743
- const active = this.library.getActiveNotebook();
1744
- if (active) {
1745
- notebookUrl = active.url;
1746
- log.info(` Using active notebook: ${active.name}`);
1747
- }
1748
- else {
1749
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1750
- }
1751
- }
1752
- // Validate URL
1753
- const safeUrl = validateNotebookUrl(notebookUrl);
1754
- // Get the shared context manager from session manager
1755
- const contextManager = this.sessionManager.getContextManager();
1756
- // Remove source
1757
- const sourceManager = new SourceManager(this.authManager, contextManager);
1758
- const result = await sourceManager.removeSource(safeUrl, args.source_id);
1759
- if (result.success) {
1760
- log.success(`✅ [TOOL] remove_source completed`);
1761
- }
1762
- else {
1763
- log.warning(`⚠️ [TOOL] remove_source failed: ${result.error}`);
1764
- }
1765
- return {
1766
- success: result.success,
1767
- data: result,
1768
- ...(result.error && { error: result.error }),
1769
- };
1770
- }
1771
- catch (error) {
1772
- const errorMessage = error instanceof Error ? error.message : String(error);
1773
- log.error(`❌ [TOOL] remove_source failed: ${errorMessage}`);
1774
- return {
1775
- success: false,
1776
- error: errorMessage,
1777
- };
1778
- }
1779
- }
1780
- /**
1781
- * Handle generate_audio_overview tool
1782
- *
1783
- * Triggers audio overview generation for a notebook.
1784
- */
1785
- async handleGenerateAudioOverview(args) {
1786
- log.info(`🔧 [TOOL] generate_audio_overview called`);
1787
- try {
1788
- // Resolve notebook URL
1789
- let notebookUrl = args.notebook_url;
1790
- if (!notebookUrl && args.notebook_id) {
1791
- const notebook = this.library.getNotebook(args.notebook_id);
1792
- if (!notebook) {
1793
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1794
- }
1795
- notebookUrl = notebook.url;
1796
- log.info(` Resolved notebook: ${notebook.name}`);
1797
- }
1798
- else if (!notebookUrl) {
1799
- const active = this.library.getActiveNotebook();
1800
- if (active) {
1801
- notebookUrl = active.url;
1802
- log.info(` Using active notebook: ${active.name}`);
1803
- }
1804
- else {
1805
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1806
- }
1807
- }
1808
- // Validate URL
1809
- const safeUrl = validateNotebookUrl(notebookUrl);
1810
- // Get the shared context manager from session manager
1811
- const contextManager = this.sessionManager.getContextManager();
1812
- // Generate audio
1813
- const audioManager = new AudioManager(this.authManager, contextManager);
1814
- const result = await audioManager.generateAudioOverview(safeUrl);
1815
- if (result.success) {
1816
- log.success(`✅ [TOOL] generate_audio_overview completed (status: ${result.status.status})`);
1817
- }
1818
- else {
1819
- log.warning(`⚠️ [TOOL] generate_audio_overview: ${result.error}`);
1820
- }
1821
- return {
1822
- success: result.success,
1823
- data: result,
1824
- ...(result.error && { error: result.error }),
1825
- };
1826
- }
1827
- catch (error) {
1828
- const errorMessage = error instanceof Error ? error.message : String(error);
1829
- log.error(`❌ [TOOL] generate_audio_overview failed: ${errorMessage}`);
1830
- return {
1831
- success: false,
1832
- error: errorMessage,
1833
- };
1834
- }
1835
- }
1836
- /**
1837
- * Handle get_audio_status tool
1838
- *
1839
- * Checks the audio generation status for a notebook.
1840
- */
1841
- async handleGetAudioStatus(args) {
1842
- log.info(`🔧 [TOOL] get_audio_status called`);
1843
- try {
1844
- // Resolve notebook URL
1845
- let notebookUrl = args.notebook_url;
1846
- if (!notebookUrl && args.notebook_id) {
1847
- const notebook = this.library.getNotebook(args.notebook_id);
1848
- if (!notebook) {
1849
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1850
- }
1851
- notebookUrl = notebook.url;
1852
- log.info(` Resolved notebook: ${notebook.name}`);
1853
- }
1854
- else if (!notebookUrl) {
1855
- const active = this.library.getActiveNotebook();
1856
- if (active) {
1857
- notebookUrl = active.url;
1858
- log.info(` Using active notebook: ${active.name}`);
1859
- }
1860
- else {
1861
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1862
- }
1863
- }
1864
- // Validate URL
1865
- const safeUrl = validateNotebookUrl(notebookUrl);
1866
- // Get the shared context manager from session manager
1867
- const contextManager = this.sessionManager.getContextManager();
1868
- // Get status
1869
- const audioManager = new AudioManager(this.authManager, contextManager);
1870
- const status = await audioManager.getAudioStatus(safeUrl);
1871
- log.success(`✅ [TOOL] get_audio_status completed (status: ${status.status})`);
1872
- return {
1873
- success: true,
1874
- data: status,
1875
- };
1876
- }
1877
- catch (error) {
1878
- const errorMessage = error instanceof Error ? error.message : String(error);
1879
- log.error(`❌ [TOOL] get_audio_status failed: ${errorMessage}`);
1880
- return {
1881
- success: false,
1882
- error: errorMessage,
1883
- };
1884
- }
1885
- }
1886
- /**
1887
- * Handle download_audio tool
1888
- *
1889
- * Downloads the generated audio file.
1890
- */
1891
- async handleDownloadAudio(args) {
1892
- log.info(`🔧 [TOOL] download_audio called`);
1893
- try {
1894
- // Resolve notebook URL
1895
- let notebookUrl = args.notebook_url;
1896
- if (!notebookUrl && args.notebook_id) {
1897
- const notebook = this.library.getNotebook(args.notebook_id);
1898
- if (!notebook) {
1899
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1900
- }
1901
- notebookUrl = notebook.url;
1902
- log.info(` Resolved notebook: ${notebook.name}`);
1903
- }
1904
- else if (!notebookUrl) {
1905
- const active = this.library.getActiveNotebook();
1906
- if (active) {
1907
- notebookUrl = active.url;
1908
- log.info(` Using active notebook: ${active.name}`);
1909
- }
1910
- else {
1911
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1912
- }
1913
- }
1914
- // Validate URL
1915
- const safeUrl = validateNotebookUrl(notebookUrl);
1916
- // Get the shared context manager from session manager
1917
- const contextManager = this.sessionManager.getContextManager();
1918
- // Download audio
1919
- const audioManager = new AudioManager(this.authManager, contextManager);
1920
- const result = await audioManager.downloadAudio(safeUrl, args.output_path);
1921
- if (result.success) {
1922
- log.success(`✅ [TOOL] download_audio completed: ${result.filePath}`);
1923
- }
1924
- else {
1925
- log.warning(`⚠️ [TOOL] download_audio: ${result.error}`);
1926
- }
1927
- return {
1928
- success: result.success,
1929
- data: result,
1930
- ...(result.error && { error: result.error }),
1931
- };
1932
- }
1933
- catch (error) {
1934
- const errorMessage = error instanceof Error ? error.message : String(error);
1935
- log.error(`❌ [TOOL] download_audio failed: ${errorMessage}`);
1936
- return {
1937
- success: false,
1938
- error: errorMessage,
1939
- };
1940
- }
1941
- }
1942
- /**
1943
- * Handle configure_webhook tool
1944
- *
1945
- * Add or update a webhook endpoint.
1946
- */
1947
- async handleConfigureWebhook(args) {
1948
- log.info(`🔧 [TOOL] configure_webhook called`);
1949
- log.info(` Name: ${args.name}`);
1950
- try {
1951
- const dispatcher = getWebhookDispatcher();
1952
- if (args.id) {
1953
- // Update existing
1954
- const updated = dispatcher.updateWebhook({
1955
- id: args.id,
1956
- name: args.name,
1957
- url: args.url,
1958
- enabled: args.enabled,
1959
- events: args.events,
1960
- format: args.format,
1961
- secret: args.secret,
1962
- });
1963
- if (!updated) {
1964
- throw new Error(`Webhook not found: ${args.id}`);
1965
- }
1966
- log.success(`✅ [TOOL] configure_webhook updated: ${updated.name}`);
1967
- return { success: true, data: updated };
1968
- }
1969
- else {
1970
- // Create new
1971
- const webhook = dispatcher.addWebhook({
1972
- name: args.name,
1973
- url: args.url,
1974
- events: args.events,
1975
- format: args.format,
1976
- secret: args.secret,
1977
- });
1978
- log.success(`✅ [TOOL] configure_webhook created: ${webhook.name}`);
1979
- return { success: true, data: webhook };
1980
- }
1981
- }
1982
- catch (error) {
1983
- const errorMessage = error instanceof Error ? error.message : String(error);
1984
- log.error(`❌ [TOOL] configure_webhook failed: ${errorMessage}`);
1985
- return { success: false, error: errorMessage };
1986
- }
1987
- }
1988
- /**
1989
- * Handle list_webhooks tool
1990
- *
1991
- * List all configured webhooks.
1992
- */
1993
- async handleListWebhooks() {
1994
- log.info(`🔧 [TOOL] list_webhooks called`);
1995
- try {
1996
- const dispatcher = getWebhookDispatcher();
1997
- const webhooks = dispatcher.listWebhooks();
1998
- const stats = dispatcher.getStats();
1999
- log.success(`✅ [TOOL] list_webhooks completed (${webhooks.length} webhooks)`);
2000
- return {
2001
- success: true,
2002
- data: { webhooks, stats },
2003
- };
2004
- }
2005
- catch (error) {
2006
- const errorMessage = error instanceof Error ? error.message : String(error);
2007
- log.error(`❌ [TOOL] list_webhooks failed: ${errorMessage}`);
2008
- return { success: false, error: errorMessage };
2009
- }
2010
- }
2011
- /**
2012
- * Handle test_webhook tool
2013
- *
2014
- * Send a test event to a webhook.
2015
- */
2016
- async handleTestWebhook(args) {
2017
- log.info(`🔧 [TOOL] test_webhook called`);
2018
- log.info(` ID: ${args.id}`);
2019
- try {
2020
- const dispatcher = getWebhookDispatcher();
2021
- const result = await dispatcher.testWebhook(args.id);
2022
- if (result.success) {
2023
- log.success(`✅ [TOOL] test_webhook succeeded`);
2024
- return {
2025
- success: true,
2026
- data: { success: true, message: "Test event delivered successfully" },
2027
- };
2028
- }
2029
- else {
2030
- log.warning(`⚠️ [TOOL] test_webhook failed: ${result.error}`);
2031
- return {
2032
- success: false,
2033
- data: { success: false, message: result.error || "Test failed" },
2034
- error: result.error,
2035
- };
2036
- }
2037
- }
2038
- catch (error) {
2039
- const errorMessage = error instanceof Error ? error.message : String(error);
2040
- log.error(`❌ [TOOL] test_webhook failed: ${errorMessage}`);
2041
- return { success: false, error: errorMessage };
2042
- }
2043
- }
2044
- /**
2045
- * Handle remove_webhook tool
2046
- *
2047
- * Remove a configured webhook.
2048
- */
2049
- async handleRemoveWebhook(args) {
2050
- log.info(`🔧 [TOOL] remove_webhook called`);
2051
- log.info(` ID: ${args.id}`);
2052
- try {
2053
- const dispatcher = getWebhookDispatcher();
2054
- const removed = dispatcher.removeWebhook(args.id);
2055
- if (removed) {
2056
- log.success(`✅ [TOOL] remove_webhook completed`);
2057
- return {
2058
- success: true,
2059
- data: { removed: true, id: args.id },
2060
- };
2061
- }
2062
- else {
2063
- log.warning(`⚠️ [TOOL] Webhook not found: ${args.id}`);
2064
- return {
2065
- success: false,
2066
- error: `Webhook not found: ${args.id}`,
2067
- };
2068
- }
2069
- }
2070
- catch (error) {
2071
- const errorMessage = error instanceof Error ? error.message : String(error);
2072
- log.error(`❌ [TOOL] remove_webhook failed: ${errorMessage}`);
2073
- return { success: false, error: errorMessage };
2074
- }
2075
- }
2076
- // ==================== GEMINI API HANDLERS ====================
2077
- /**
2078
- * Handle deep_research tool
2079
- *
2080
- * Performs comprehensive research using Gemini's Deep Research agent.
2081
- */
2082
- async handleDeepResearch(args, sendProgress) {
2083
- const startTime = Date.now();
2084
- log.info(`🔧 [TOOL] deep_research called`);
2085
- log.info(` Query: "${sanitizeForLogging(args.query.substring(0, 100))}"...`);
2086
- // Check if Gemini is available
2087
- if (!this.geminiClient.isAvailable()) {
2088
- log.error(`❌ [TOOL] deep_research failed: Gemini API key not configured`);
2089
- return {
2090
- success: false,
2091
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2092
- };
2093
- }
2094
- try {
2095
- // Validate query
2096
- if (!args.query || args.query.trim().length === 0) {
2097
- throw new Error("Query cannot be empty");
2098
- }
2099
- if (args.query.length > 10000) {
2100
- throw new Error("Query too long (max 10000 characters)");
2101
- }
2102
- // Validate max_wait_seconds
2103
- const maxWaitSeconds = Math.min(args.max_wait_seconds || 300, 600); // Max 10 minutes
2104
- const maxWaitMs = maxWaitSeconds * 1000;
2105
- if (sendProgress) {
2106
- await sendProgress("Starting deep research...", 0, 100);
2107
- }
2108
- // Start the research
2109
- const interaction = await this.geminiClient.deepResearch({
2110
- query: args.query,
2111
- background: true,
2112
- waitForCompletion: args.wait_for_completion !== false,
2113
- maxWaitMs,
2114
- progressCallback: sendProgress,
2115
- });
2116
- const durationMs = Date.now() - startTime;
2117
- // Extract the answer
2118
- const answer = interaction.outputs.find(o => o.type === "text")?.text || "";
2119
- // Audit log
2120
- await audit.tool("deep_research", { query: sanitizeForLogging(args.query) }, true, durationMs);
2121
- log.success(`✅ [TOOL] deep_research completed in ${durationMs}ms`);
2122
- return {
2123
- success: true,
2124
- data: {
2125
- interactionId: interaction.id,
2126
- status: interaction.status,
2127
- answer,
2128
- tokensUsed: interaction.usage?.totalTokens,
2129
- durationMs,
2130
- ...(interaction.deprecationWarning && { deprecationWarning: interaction.deprecationWarning }),
2131
- },
2132
- };
2133
- }
2134
- catch (error) {
2135
- const errorMessage = error instanceof Error ? error.message : String(error);
2136
- const durationMs = Date.now() - startTime;
2137
- log.error(`❌ [TOOL] deep_research failed: ${errorMessage}`);
2138
- await audit.tool("deep_research", { query: sanitizeForLogging(args.query) }, false, durationMs, errorMessage);
2139
- return { success: false, error: errorMessage };
2140
- }
2141
- }
2142
- /**
2143
- * Handle gemini_query tool
2144
- *
2145
- * Quick query to Gemini model with optional grounding tools.
2146
- */
2147
- async handleGeminiQuery(args) {
2148
- const startTime = Date.now();
2149
- log.info(`🔧 [TOOL] gemini_query called`);
2150
- log.info(` Query: "${sanitizeForLogging(args.query.substring(0, 100))}"...`);
2151
- log.info(` Model: ${args.model || "default"}`);
2152
- if (args.tools)
2153
- log.info(` Tools: ${args.tools.join(", ")}`);
2154
- // Check if Gemini is available
2155
- if (!this.geminiClient.isAvailable()) {
2156
- log.error(`❌ [TOOL] gemini_query failed: Gemini API key not configured`);
2157
- return {
2158
- success: false,
2159
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2160
- };
2161
- }
2162
- try {
2163
- // Validate query
2164
- if (!args.query || args.query.trim().length === 0) {
2165
- throw new Error("Query cannot be empty");
2166
- }
2167
- if (args.query.length > 30000) {
2168
- throw new Error("Query too long (max 30000 characters)");
2169
- }
2170
- // If URLs provided, auto-enable url_context
2171
- let tools = args.tools || [];
2172
- if (args.urls && args.urls.length > 0 && !tools.includes("url_context")) {
2173
- tools = [...tools, "url_context"];
2174
- }
2175
- // Validate URLs if provided
2176
- if (args.urls) {
2177
- for (const url of args.urls) {
2178
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
2179
- throw new Error(`Invalid URL: ${url} (must start with http:// or https://)`);
2180
- }
2181
- }
2182
- }
2183
- // Build generationConfig from thinking_level and response_schema
2184
- const hasGenConfig = args.thinking_level || args.response_schema;
2185
- const generationConfig = hasGenConfig ? {
2186
- ...(args.thinking_level && { thinkingLevel: args.thinking_level }),
2187
- ...(args.response_schema && {
2188
- responseMimeType: "application/json",
2189
- responseSchema: args.response_schema,
2190
- }),
2191
- } : undefined;
2192
- const interaction = await this.geminiClient.query({
2193
- query: args.query,
2194
- model: args.model,
2195
- tools,
2196
- urls: args.urls,
2197
- previousInteractionId: args.previous_interaction_id,
2198
- generationConfig,
2199
- });
2200
- const durationMs = Date.now() - startTime;
2201
- // Extract the answer
2202
- const answer = interaction.outputs.find(o => o.type === "text")?.text || "";
2203
- // Identify which tools were used
2204
- const toolsUsed = interaction.outputs
2205
- .filter(o => o.type === "function_call")
2206
- .map(o => o.name)
2207
- .filter((name) => !!name);
2208
- // Audit log
2209
- await audit.tool("gemini_query", {
2210
- query: sanitizeForLogging(args.query),
2211
- model: args.model,
2212
- tools: args.tools,
2213
- }, true, durationMs);
2214
- log.success(`✅ [TOOL] gemini_query completed in ${durationMs}ms`);
2215
- return {
2216
- success: true,
2217
- data: {
2218
- interactionId: interaction.id,
2219
- answer,
2220
- model: interaction.model || args.model || CONFIG.geminiDefaultModel,
2221
- tokensUsed: interaction.usage?.totalTokens,
2222
- toolsUsed: toolsUsed.length > 0 ? toolsUsed : undefined,
2223
- ...(interaction.deprecationWarning && { deprecationWarning: interaction.deprecationWarning }),
2224
- },
2225
- };
2226
- }
2227
- catch (error) {
2228
- const errorMessage = error instanceof Error ? error.message : String(error);
2229
- const durationMs = Date.now() - startTime;
2230
- log.error(`❌ [TOOL] gemini_query failed: ${errorMessage}`);
2231
- await audit.tool("gemini_query", { query: sanitizeForLogging(args.query) }, false, durationMs, errorMessage);
2232
- return { success: false, error: errorMessage };
2233
- }
2234
- }
2235
- /**
2236
- * Handle get_research_status tool
2237
- *
2238
- * Check the status of a background deep research task.
2239
- */
2240
- async handleGetResearchStatus(args) {
2241
- log.info(`🔧 [TOOL] get_research_status called`);
2242
- log.info(` Interaction ID: ${args.interaction_id}`);
2243
- // Check if Gemini is available
2244
- if (!this.geminiClient.isAvailable()) {
2245
- log.error(`❌ [TOOL] get_research_status failed: Gemini API key not configured`);
2246
- return {
2247
- success: false,
2248
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2249
- };
2250
- }
2251
- try {
2252
- // Validate interaction_id
2253
- if (!args.interaction_id || args.interaction_id.trim().length === 0) {
2254
- throw new Error("Interaction ID cannot be empty");
2255
- }
2256
- const interaction = await this.geminiClient.getInteraction(args.interaction_id);
2257
- log.success(`✅ [TOOL] get_research_status: ${interaction.status}`);
2258
- return {
2259
- success: true,
2260
- data: interaction,
2261
- };
2262
- }
2263
- catch (error) {
2264
- const errorMessage = error instanceof Error ? error.message : String(error);
2265
- log.error(`❌ [TOOL] get_research_status failed: ${errorMessage}`);
2266
- return { success: false, error: errorMessage };
2267
- }
2268
- }
2269
- // ==================== DOCUMENT TOOLS (v1.9.0) ====================
2270
- /**
2271
- * Upload a document to Gemini Files API
2272
- */
2273
- async handleUploadDocument(args) {
2274
- const startTime = Date.now();
2275
- log.info(`🔧 [TOOL] upload_document called`);
2276
- log.info(` File: ${args.file_path}`);
2277
- // Check if Gemini is available
2278
- if (!this.geminiClient.isAvailable()) {
2279
- log.error(`❌ [TOOL] upload_document failed: Gemini API key not configured`);
2280
- return {
2281
- success: false,
2282
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2283
- };
2284
- }
2285
- try {
2286
- // Validate file path
2287
- if (!args.file_path || args.file_path.trim().length === 0) {
2288
- throw new Error("File path cannot be empty");
2289
- }
2290
- const result = await this.geminiClient.uploadDocument({
2291
- filePath: args.file_path,
2292
- displayName: args.display_name,
2293
- });
2294
- const durationMs = Date.now() - startTime;
2295
- await audit.tool("upload_document", { file: sanitizeForLogging(args.file_path) }, true, durationMs);
2296
- log.success(`✅ [TOOL] upload_document completed in ${durationMs}ms`);
2297
- return {
2298
- success: true,
2299
- data: result,
2300
- };
2301
- }
2302
- catch (error) {
2303
- const errorMessage = error instanceof Error ? error.message : String(error);
2304
- const durationMs = Date.now() - startTime;
2305
- await audit.tool("upload_document", { file: sanitizeForLogging(args.file_path) }, false, durationMs, errorMessage);
2306
- log.error(`❌ [TOOL] upload_document failed: ${errorMessage}`);
2307
- return { success: false, error: errorMessage };
2308
- }
2309
- }
2310
- /**
2311
- * Query an uploaded document
2312
- */
2313
- async handleQueryDocument(args) {
2314
- const startTime = Date.now();
2315
- log.info(`🔧 [TOOL] query_document called`);
2316
- log.info(` File: ${args.file_name}`);
2317
- log.info(` Query: ${args.query.substring(0, 50)}...`);
2318
- // Check if Gemini is available
2319
- if (!this.geminiClient.isAvailable()) {
2320
- log.error(`❌ [TOOL] query_document failed: Gemini API key not configured`);
2321
- return {
2322
- success: false,
2323
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2324
- };
2325
- }
2326
- try {
2327
- // Validate inputs
2328
- if (!args.file_name || args.file_name.trim().length === 0) {
2329
- throw new Error("File name cannot be empty");
2330
- }
2331
- if (!args.query || args.query.trim().length === 0) {
2332
- throw new Error("Query cannot be empty");
2333
- }
2334
- const result = await this.geminiClient.queryDocument({
2335
- fileName: args.file_name,
2336
- query: args.query,
2337
- model: args.model,
2338
- additionalFiles: args.additional_files,
2339
- });
2340
- const durationMs = Date.now() - startTime;
2341
- await audit.tool("query_document", { file: args.file_name, query: sanitizeForLogging(args.query) }, true, durationMs);
2342
- log.success(`✅ [TOOL] query_document completed in ${durationMs}ms`);
2343
- return {
2344
- success: true,
2345
- data: result,
2346
- };
2347
- }
2348
- catch (error) {
2349
- const errorMessage = error instanceof Error ? error.message : String(error);
2350
- const durationMs = Date.now() - startTime;
2351
- await audit.tool("query_document", { file: args.file_name }, false, durationMs, errorMessage);
2352
- log.error(`❌ [TOOL] query_document failed: ${errorMessage}`);
2353
- return { success: false, error: errorMessage };
2354
- }
2355
- }
2356
- /**
2357
- * List all uploaded documents
2358
- */
2359
- async handleListDocuments(args) {
2360
- log.info(`🔧 [TOOL] list_documents called`);
2361
- // Check if Gemini is available
2362
- if (!this.geminiClient.isAvailable()) {
2363
- log.error(`❌ [TOOL] list_documents failed: Gemini API key not configured`);
2364
- return {
2365
- success: false,
2366
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2367
- };
2368
- }
2369
- try {
2370
- const result = await this.geminiClient.listFiles(args.page_size || 100);
2371
- log.success(`✅ [TOOL] list_documents: ${result.totalCount} files`);
2372
- return {
2373
- success: true,
2374
- data: result,
2375
- };
2376
- }
2377
- catch (error) {
2378
- const errorMessage = error instanceof Error ? error.message : String(error);
2379
- log.error(`❌ [TOOL] list_documents failed: ${errorMessage}`);
2380
- return { success: false, error: errorMessage };
2381
- }
2382
- }
2383
- /**
2384
- * Delete an uploaded document
2385
- */
2386
- async handleDeleteDocument(args) {
2387
- log.info(`🔧 [TOOL] delete_document called`);
2388
- log.info(` File: ${args.file_name}`);
2389
- // Check if Gemini is available
2390
- if (!this.geminiClient.isAvailable()) {
2391
- log.error(`❌ [TOOL] delete_document failed: Gemini API key not configured`);
2392
- return {
2393
- success: false,
2394
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2395
- };
2396
- }
2397
- try {
2398
- // Validate file name
2399
- if (!args.file_name || args.file_name.trim().length === 0) {
2400
- throw new Error("File name cannot be empty");
2401
- }
2402
- await this.geminiClient.deleteFile(args.file_name);
2403
- log.success(`✅ [TOOL] delete_document: ${args.file_name} deleted`);
2404
- return {
2405
- success: true,
2406
- data: { deleted: true, fileName: args.file_name },
2407
- };
2408
- }
2409
- catch (error) {
2410
- const errorMessage = error instanceof Error ? error.message : String(error);
2411
- log.error(`❌ [TOOL] delete_document failed: ${errorMessage}`);
2412
- return { success: false, error: errorMessage };
2413
- }
2414
- }
2415
- /**
2416
- * Query a chunked document (v1.10.0)
2417
- * Queries multiple chunks and aggregates results
2418
- */
2419
- async handleQueryChunkedDocument(args) {
2420
- log.info(`🔧 [TOOL] query_chunked_document called`);
2421
- log.info(` Chunks: ${args.file_names.length}`);
2422
- log.info(` Query: ${args.query.substring(0, 50)}...`);
2423
- // Check if Gemini is available
2424
- if (!this.geminiClient.isAvailable()) {
2425
- log.error(`❌ [TOOL] query_chunked_document failed: Gemini API key not configured`);
2426
- return {
2427
- success: false,
2428
- error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
2429
- };
2430
- }
2431
- try {
2432
- // Validate inputs
2433
- if (!args.file_names || args.file_names.length === 0) {
2434
- throw new Error("At least one file name is required");
2435
- }
2436
- if (!args.query || args.query.trim().length === 0) {
2437
- throw new Error("Query cannot be empty");
2438
- }
2439
- const result = await this.geminiClient.queryChunkedDocument(args.file_names, args.query, { model: args.model });
2440
- log.success(`✅ [TOOL] query_chunked_document completed`);
2441
- return {
2442
- success: true,
2443
- data: {
2444
- answer: result.answer,
2445
- model: result.model,
2446
- tokensUsed: result.tokensUsed,
2447
- chunksQueried: args.file_names.length,
2448
- filesUsed: result.filesUsed,
2449
- },
2450
- };
2451
- }
2452
- catch (error) {
2453
- const errorMessage = error instanceof Error ? error.message : String(error);
2454
- log.error(`❌ [TOOL] query_chunked_document failed: ${errorMessage}`);
2455
- return { success: false, error: errorMessage };
2456
- }
2457
- }
2458
- // ==================== QUERY HISTORY ====================
2459
- /**
2460
- * Handle get_query_history tool
2461
- *
2462
- * Retrieves past NotebookLM queries for reviewing research sessions.
2463
- */
2464
- async handleGetQueryHistory(args) {
2465
- log.info(`🔧 [TOOL] get_query_history called`);
2466
- try {
2467
- const queryLogger = getQueryLogger();
2468
- const limit = Math.min(args.limit ?? 50, 500); // Cap at 500
2469
- let queries;
2470
- if (args.search) {
2471
- // Search across all queries
2472
- queries = await queryLogger.searchQueries(args.search, { limit });
2473
- log.info(` Searching for: "${args.search}"`);
2474
- }
2475
- else if (args.session_id) {
2476
- // Filter by session
2477
- queries = await queryLogger.getQueriesForSession(args.session_id);
2478
- log.info(` Filtering by session: ${args.session_id}`);
2479
- }
2480
- else if (args.notebook_id) {
2481
- // Filter by notebook
2482
- queries = await queryLogger.getQueriesForNotebookId(args.notebook_id);
2483
- log.info(` Filtering by notebook: ${args.notebook_id}`);
2484
- }
2485
- else if (args.date) {
2486
- // Filter by date
2487
- queries = await queryLogger.getQueriesForDate(args.date);
2488
- log.info(` Filtering by date: ${args.date}`);
2489
- }
2490
- else {
2491
- // Get recent queries
2492
- queries = await queryLogger.getRecentQueries(limit);
2493
- log.info(` Getting recent queries (limit: ${limit})`);
2494
- }
2495
- // Apply limit
2496
- const limitedQueries = queries.slice(0, limit);
2497
- log.success(`✅ [TOOL] get_query_history completed (${limitedQueries.length} queries)`);
2498
- return {
2499
- success: true,
2500
- data: {
2501
- count: limitedQueries.length,
2502
- queries: limitedQueries,
2503
- },
2504
- };
2505
- }
2506
- catch (error) {
2507
- const errorMessage = error instanceof Error ? error.message : String(error);
2508
- log.error(`❌ [TOOL] get_query_history failed: ${errorMessage}`);
2509
- return {
2510
- success: false,
2511
- error: errorMessage,
2512
- };
2513
- }
2514
- }
2515
- // ==================== CHAT HISTORY ====================
2516
- /**
2517
- * Handle get_notebook_chat_history tool
2518
- *
2519
- * Extracts conversation history from a NotebookLM notebook's chat UI
2520
- * using browser automation.
2521
- */
2522
- async handleGetNotebookChatHistory(args) {
2523
- log.info(`🔧 [TOOL] get_notebook_chat_history called${args.preview_only ? ' (preview mode)' : ''}`);
2524
- try {
2525
- // Resolve notebook URL
2526
- let notebookUrl;
2527
- let notebookName;
2528
- if (args.notebook_url) {
2529
- notebookUrl = validateNotebookUrl(args.notebook_url);
2530
- }
2531
- else if (args.notebook_id) {
2532
- validateNotebookId(args.notebook_id);
2533
- const notebook = this.library.getNotebook(args.notebook_id);
2534
- if (!notebook) {
2535
- return {
2536
- success: false,
2537
- error: `Notebook not found: ${args.notebook_id}. Use list_notebooks to see available notebooks.`,
2538
- };
2539
- }
2540
- notebookUrl = notebook.url;
2541
- notebookName = notebook.name;
2542
- }
2543
- else {
2544
- // Try to use active notebook
2545
- const activeNotebook = this.library.getActiveNotebook();
2546
- if (!activeNotebook) {
2547
- return {
2548
- success: false,
2549
- error: "No notebook specified. Provide notebook_id or notebook_url, or set an active notebook.",
2550
- };
2551
- }
2552
- notebookUrl = activeNotebook.url;
2553
- notebookName = activeNotebook.name;
2554
- }
2555
- log.info(` 📓 Extracting chat history from: ${notebookUrl}`);
2556
- // Apply browser options if show_browser is set
2557
- if (args.show_browser !== undefined) {
2558
- applyBrowserOptions({ show: args.show_browser });
2559
- }
2560
- // Create a temporary session to navigate to the notebook
2561
- const sessionId = `chat-history-${Date.now()}`;
2562
- const session = await this.sessionManager.getOrCreateSession(sessionId, notebookUrl);
2563
- try {
2564
- // Get the page from the session
2565
- const page = session.getPage();
2566
- if (!page) {
2567
- throw new Error("Failed to get page from session");
2568
- }
2569
- // Wait a bit for the chat history to fully load
2570
- await page.waitForTimeout(2000);
2571
- const messages = await page.evaluate(() => {
2572
- const result = [];
2573
- // Get all message containers (both user and assistant)
2574
- // User messages: .from-user-container / Assistant messages: .to-user-container
2575
- // @ts-expect-error - DOM types available in browser context
2576
- const allContainers = document.querySelectorAll(".from-user-container, .to-user-container");
2577
- let idx = 0;
2578
- allContainers.forEach((container) => {
2579
- const isUser = container.classList?.contains("from-user-container");
2580
- const isAssistant = container.classList?.contains("to-user-container");
2581
- if (isUser) {
2582
- // User message - look for query text
2583
- const queryText = container.querySelector(".query-text, .message-text-content, .user-message");
2584
- if (queryText) {
2585
- const content = queryText.innerText?.trim();
2586
- if (content) {
2587
- result.push({ role: "user", content, index: idx++ });
2588
- }
2589
- }
2590
- else {
2591
- // Fallback: get container text directly
2592
- const content = container.innerText?.trim();
2593
- if (content) {
2594
- result.push({ role: "user", content, index: idx++ });
2595
- }
2596
- }
2597
- }
2598
- else if (isAssistant) {
2599
- // Assistant message
2600
- const textContent = container.querySelector(".message-text-content");
2601
- if (textContent) {
2602
- const content = textContent.innerText?.trim();
2603
- if (content) {
2604
- result.push({ role: "assistant", content, index: idx++ });
2605
- }
2606
- }
2607
- }
2608
- });
2609
- return result;
2610
- });
2611
- // Calculate stats
2612
- const totalMessages = messages.length;
2613
- const userMessages = messages.filter(m => m.role === "user").length;
2614
- const assistantMessages = messages.filter(m => m.role === "assistant").length;
2615
- // Preview mode - just return stats without content
2616
- if (args.preview_only) {
2617
- log.success(`✅ [TOOL] get_notebook_chat_history preview completed (${totalMessages} messages found)`);
2618
- return {
2619
- success: true,
2620
- data: {
2621
- notebook_url: notebookUrl,
2622
- notebook_name: notebookName,
2623
- total_messages: totalMessages,
2624
- returned_messages: 0,
2625
- user_messages: userMessages,
2626
- assistant_messages: assistantMessages,
2627
- },
2628
- };
2629
- }
2630
- // Apply pagination (offset and limit)
2631
- const offset = args.offset ?? 0;
2632
- const limit = Math.min(args.limit ?? 50, 200);
2633
- const startIdx = offset * 2; // offset is in pairs, convert to message count
2634
- const endIdx = startIdx + (limit * 2);
2635
- const paginatedMessages = messages.slice(startIdx, endIdx);
2636
- const hasMore = endIdx < totalMessages;
2637
- // Re-index the paginated messages
2638
- const reindexedMessages = paginatedMessages.map((m, idx) => ({
2639
- ...m,
2640
- index: startIdx + idx,
2641
- }));
2642
- // Export to file if requested
2643
- if (args.output_file) {
2644
- const fs = await import("fs/promises");
2645
- const exportData = {
2646
- notebook_url: notebookUrl,
2647
- notebook_name: notebookName,
2648
- exported_at: new Date().toISOString(),
2649
- total_messages: totalMessages,
2650
- user_messages: userMessages,
2651
- assistant_messages: assistantMessages,
2652
- messages: reindexedMessages,
2653
- };
2654
- await fs.writeFile(args.output_file, JSON.stringify(exportData, null, 2));
2655
- log.success(`✅ [TOOL] get_notebook_chat_history exported to ${args.output_file}`);
2656
- return {
2657
- success: true,
2658
- data: {
2659
- notebook_url: notebookUrl,
2660
- notebook_name: notebookName,
2661
- total_messages: totalMessages,
2662
- returned_messages: reindexedMessages.length,
2663
- user_messages: userMessages,
2664
- assistant_messages: assistantMessages,
2665
- output_file: args.output_file,
2666
- },
2667
- };
2668
- }
2669
- log.success(`✅ [TOOL] get_notebook_chat_history completed (${reindexedMessages.length}/${totalMessages} messages)`);
2670
- return {
2671
- success: true,
2672
- data: {
2673
- notebook_url: notebookUrl,
2674
- notebook_name: notebookName,
2675
- total_messages: totalMessages,
2676
- returned_messages: reindexedMessages.length,
2677
- user_messages: userMessages,
2678
- assistant_messages: assistantMessages,
2679
- offset: offset,
2680
- has_more: hasMore,
2681
- messages: reindexedMessages,
2682
- },
2683
- };
2684
- }
2685
- finally {
2686
- // Close the temporary session
2687
- await this.sessionManager.closeSession(sessionId);
2688
- }
2689
- }
2690
- catch (error) {
2691
- const errorMessage = error instanceof Error ? error.message : String(error);
2692
- log.error(`❌ [TOOL] get_notebook_chat_history failed: ${errorMessage}`);
2693
- return {
2694
- success: false,
2695
- error: errorMessage,
2696
- };
2697
- }
2698
- }
2699
- // ==================== VIDEO OVERVIEW ====================
2700
- /**
2701
- * Handle generate_video_overview tool
2702
- *
2703
- * Generates a Video Overview via the Studio panel in NotebookLM.
2704
- */
2705
- async handleGenerateVideoOverview(args) {
2706
- log.info(`🔧 [TOOL] generate_video_overview called`);
2707
- try {
2708
- // Resolve notebook URL
2709
- let notebookUrl = args.notebook_url;
2710
- if (!notebookUrl && args.notebook_id) {
2711
- const notebook = this.library.getNotebook(args.notebook_id);
2712
- if (!notebook) {
2713
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
2714
- }
2715
- notebookUrl = notebook.url;
2716
- log.info(` Resolved notebook: ${notebook.name}`);
2717
- }
2718
- else if (!notebookUrl) {
2719
- const active = this.library.getActiveNotebook();
2720
- if (active) {
2721
- notebookUrl = active.url;
2722
- log.info(` Using active notebook: ${active.name}`);
2723
- }
2724
- else {
2725
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
2726
- }
2727
- }
2728
- // Validate URL
2729
- const safeUrl = validateNotebookUrl(notebookUrl);
2730
- // Get the shared context manager from session manager
2731
- const contextManager = this.sessionManager.getContextManager();
2732
- // Generate video
2733
- const videoManager = new VideoManager(this.authManager, contextManager);
2734
- const result = await videoManager.generateVideoOverview(safeUrl, args.style || "auto-select", args.format || "explainer");
2735
- if (result.success) {
2736
- log.success(`✅ [TOOL] generate_video_overview completed (status: ${result.status.status})`);
2737
- }
2738
- else {
2739
- log.warning(`⚠️ [TOOL] generate_video_overview: ${result.error}`);
2740
- }
2741
- return {
2742
- success: result.success,
2743
- data: result,
2744
- ...(result.error && { error: result.error }),
2745
- };
2746
- }
2747
- catch (error) {
2748
- const errorMessage = error instanceof Error ? error.message : String(error);
2749
- log.error(`❌ [TOOL] generate_video_overview failed: ${errorMessage}`);
2750
- return {
2751
- success: false,
2752
- error: errorMessage,
2753
- };
2754
- }
2755
- }
2756
- /**
2757
- * Handle get_video_status tool
2758
- *
2759
- * Checks the video generation status for a notebook.
2760
- */
2761
- async handleGetVideoStatus(args) {
2762
- log.info(`🔧 [TOOL] get_video_status called`);
2763
- try {
2764
- // Resolve notebook URL
2765
- let notebookUrl = args.notebook_url;
2766
- if (!notebookUrl && args.notebook_id) {
2767
- const notebook = this.library.getNotebook(args.notebook_id);
2768
- if (!notebook) {
2769
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
2770
- }
2771
- notebookUrl = notebook.url;
2772
- log.info(` Resolved notebook: ${notebook.name}`);
2773
- }
2774
- else if (!notebookUrl) {
2775
- const active = this.library.getActiveNotebook();
2776
- if (active) {
2777
- notebookUrl = active.url;
2778
- log.info(` Using active notebook: ${active.name}`);
2779
- }
2780
- else {
2781
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
2782
- }
2783
- }
2784
- // Validate URL
2785
- const safeUrl = validateNotebookUrl(notebookUrl);
2786
- // Get the shared context manager from session manager
2787
- const contextManager = this.sessionManager.getContextManager();
2788
- // Get status
2789
- const videoManager = new VideoManager(this.authManager, contextManager);
2790
- const status = await videoManager.getVideoStatus(safeUrl);
2791
- log.success(`✅ [TOOL] get_video_status completed (status: ${status.status})`);
2792
- return {
2793
- success: true,
2794
- data: status,
2795
- };
2796
- }
2797
- catch (error) {
2798
- const errorMessage = error instanceof Error ? error.message : String(error);
2799
- log.error(`❌ [TOOL] get_video_status failed: ${errorMessage}`);
2800
- return {
2801
- success: false,
2802
- error: errorMessage,
2803
- };
2804
- }
2805
- }
2806
- // ==================== DATA TABLES ====================
2807
- /**
2808
- * Handle generate_data_table tool
2809
- *
2810
- * Generates a structured Data Table via the Studio panel in NotebookLM.
2811
- */
2812
- async handleGenerateDataTable(args) {
2813
- log.info(`🔧 [TOOL] generate_data_table called`);
2814
- try {
2815
- // Resolve notebook URL
2816
- let notebookUrl = args.notebook_url;
2817
- if (!notebookUrl && args.notebook_id) {
2818
- const notebook = this.library.getNotebook(args.notebook_id);
2819
- if (!notebook) {
2820
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
2821
- }
2822
- notebookUrl = notebook.url;
2823
- log.info(` Resolved notebook: ${notebook.name}`);
2824
- }
2825
- else if (!notebookUrl) {
2826
- const active = this.library.getActiveNotebook();
2827
- if (active) {
2828
- notebookUrl = active.url;
2829
- log.info(` Using active notebook: ${active.name}`);
2830
- }
2831
- else {
2832
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
2833
- }
2834
- }
2835
- // Validate URL
2836
- const safeUrl = validateNotebookUrl(notebookUrl);
2837
- // Get the shared context manager from session manager
2838
- const contextManager = this.sessionManager.getContextManager();
2839
- // Generate data table
2840
- const dataTableManager = new DataTableManager(this.authManager, contextManager);
2841
- const result = await dataTableManager.generateDataTable(safeUrl);
2842
- if (result.success) {
2843
- log.success(`✅ [TOOL] generate_data_table completed (status: ${result.status.status})`);
2844
- }
2845
- else {
2846
- log.warning(`⚠️ [TOOL] generate_data_table: ${result.error}`);
2847
- }
2848
- return {
2849
- success: result.success,
2850
- data: result,
2851
- ...(result.error && { error: result.error }),
2852
- };
2853
- }
2854
- catch (error) {
2855
- const errorMessage = error instanceof Error ? error.message : String(error);
2856
- log.error(`❌ [TOOL] generate_data_table failed: ${errorMessage}`);
2857
- return {
2858
- success: false,
2859
- error: errorMessage,
2860
- };
2861
- }
2862
- }
2863
- /**
2864
- * Handle get_data_table tool
2865
- *
2866
- * Extracts the generated Data Table content from a notebook.
2867
- */
2868
- async handleGetDataTable(args) {
2869
- log.info(`🔧 [TOOL] get_data_table called`);
2870
- try {
2871
- // Resolve notebook URL
2872
- let notebookUrl = args.notebook_url;
2873
- if (!notebookUrl && args.notebook_id) {
2874
- const notebook = this.library.getNotebook(args.notebook_id);
2875
- if (!notebook) {
2876
- throw new Error(`Notebook not found in library: ${args.notebook_id}`);
2877
- }
2878
- notebookUrl = notebook.url;
2879
- log.info(` Resolved notebook: ${notebook.name}`);
2880
- }
2881
- else if (!notebookUrl) {
2882
- const active = this.library.getActiveNotebook();
2883
- if (active) {
2884
- notebookUrl = active.url;
2885
- log.info(` Using active notebook: ${active.name}`);
2886
- }
2887
- else {
2888
- throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
2889
- }
2890
- }
2891
- // Validate URL
2892
- const safeUrl = validateNotebookUrl(notebookUrl);
2893
- // Get the shared context manager from session manager
2894
- const contextManager = this.sessionManager.getContextManager();
2895
- // Get data table
2896
- const dataTableManager = new DataTableManager(this.authManager, contextManager);
2897
- const result = await dataTableManager.getDataTable(safeUrl);
2898
- if (result.success) {
2899
- log.success(`✅ [TOOL] get_data_table completed (${result.table?.totalRows} rows x ${result.table?.totalColumns} cols)`);
2900
- }
2901
- else {
2902
- log.warning(`⚠️ [TOOL] get_data_table: ${result.error}`);
2903
- }
2904
- return {
2905
- success: result.success,
2906
- data: result,
2907
- ...(result.error && { error: result.error }),
2908
- };
2909
- }
2910
- catch (error) {
2911
- const errorMessage = error instanceof Error ? error.message : String(error);
2912
- log.error(`❌ [TOOL] get_data_table failed: ${errorMessage}`);
2913
- return {
2914
- success: false,
2915
- error: errorMessage,
2916
- };
2917
- }
2918
- }
2919
- // ==================== CLEANUP ====================
2920
- /**
2921
- * Cleanup all resources (called on server shutdown)
2922
- */
2923
- async cleanup() {
2924
- log.info(`🧹 Cleaning up tool handlers...`);
2925
- await this.sessionManager.closeAllSessions();
2926
- log.success(`✅ Tool handlers cleanup complete`);
2927
- }
2928
- }
2929
- //# sourceMappingURL=handlers.js.map