@pan-sec/notebooklm-mcp 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/events/event-emitter.d.ts +45 -0
  2. package/dist/events/event-emitter.d.ts.map +1 -0
  3. package/dist/events/event-emitter.js +100 -0
  4. package/dist/events/event-emitter.js.map +1 -0
  5. package/dist/events/event-types.d.ts +124 -0
  6. package/dist/events/event-types.d.ts.map +1 -0
  7. package/dist/events/event-types.js +18 -0
  8. package/dist/events/event-types.js.map +1 -0
  9. package/dist/index.js +59 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/library/notebook-library.d.ts +25 -2
  12. package/dist/library/notebook-library.d.ts.map +1 -1
  13. package/dist/library/notebook-library.js +142 -2
  14. package/dist/library/notebook-library.js.map +1 -1
  15. package/dist/library/types.d.ts +15 -0
  16. package/dist/library/types.d.ts.map +1 -1
  17. package/dist/notebook-creation/audio-manager.d.ts +56 -0
  18. package/dist/notebook-creation/audio-manager.d.ts.map +1 -0
  19. package/dist/notebook-creation/audio-manager.js +335 -0
  20. package/dist/notebook-creation/audio-manager.js.map +1 -0
  21. package/dist/notebook-creation/discover-creation-flow.d.ts +8 -0
  22. package/dist/notebook-creation/discover-creation-flow.d.ts.map +1 -0
  23. package/dist/notebook-creation/discover-creation-flow.js +177 -0
  24. package/dist/notebook-creation/discover-creation-flow.js.map +1 -0
  25. package/dist/notebook-creation/discover-quota.d.ts +8 -0
  26. package/dist/notebook-creation/discover-quota.d.ts.map +1 -0
  27. package/dist/notebook-creation/discover-quota.js +195 -0
  28. package/dist/notebook-creation/discover-quota.js.map +1 -0
  29. package/dist/notebook-creation/discover-source-dialog.d.ts +8 -0
  30. package/dist/notebook-creation/discover-source-dialog.d.ts.map +1 -0
  31. package/dist/notebook-creation/discover-source-dialog.js +134 -0
  32. package/dist/notebook-creation/discover-source-dialog.js.map +1 -0
  33. package/dist/notebook-creation/discover-sources.d.ts +8 -0
  34. package/dist/notebook-creation/discover-sources.d.ts.map +1 -0
  35. package/dist/notebook-creation/discover-sources.js +273 -0
  36. package/dist/notebook-creation/discover-sources.js.map +1 -0
  37. package/dist/notebook-creation/discover-text-input.d.ts +7 -0
  38. package/dist/notebook-creation/discover-text-input.d.ts.map +1 -0
  39. package/dist/notebook-creation/discover-text-input.js +135 -0
  40. package/dist/notebook-creation/discover-text-input.js.map +1 -0
  41. package/dist/notebook-creation/index.d.ts +12 -0
  42. package/dist/notebook-creation/index.d.ts.map +1 -0
  43. package/dist/notebook-creation/index.js +12 -0
  44. package/dist/notebook-creation/index.js.map +1 -0
  45. package/dist/notebook-creation/notebook-creator.d.ts +95 -0
  46. package/dist/notebook-creation/notebook-creator.d.ts.map +1 -0
  47. package/dist/notebook-creation/notebook-creator.js +689 -0
  48. package/dist/notebook-creation/notebook-creator.js.map +1 -0
  49. package/dist/notebook-creation/notebook-sync.d.ts +93 -0
  50. package/dist/notebook-creation/notebook-sync.d.ts.map +1 -0
  51. package/dist/notebook-creation/notebook-sync.js +370 -0
  52. package/dist/notebook-creation/notebook-sync.js.map +1 -0
  53. package/dist/notebook-creation/run-discovery.d.ts +11 -0
  54. package/dist/notebook-creation/run-discovery.d.ts.map +1 -0
  55. package/dist/notebook-creation/run-discovery.js +151 -0
  56. package/dist/notebook-creation/run-discovery.js.map +1 -0
  57. package/dist/notebook-creation/selector-discovery.d.ts +65 -0
  58. package/dist/notebook-creation/selector-discovery.d.ts.map +1 -0
  59. package/dist/notebook-creation/selector-discovery.js +421 -0
  60. package/dist/notebook-creation/selector-discovery.js.map +1 -0
  61. package/dist/notebook-creation/selectors.d.ts +150 -0
  62. package/dist/notebook-creation/selectors.d.ts.map +1 -0
  63. package/dist/notebook-creation/selectors.js +225 -0
  64. package/dist/notebook-creation/selectors.js.map +1 -0
  65. package/dist/notebook-creation/source-manager.d.ts +73 -0
  66. package/dist/notebook-creation/source-manager.d.ts.map +1 -0
  67. package/dist/notebook-creation/source-manager.js +486 -0
  68. package/dist/notebook-creation/source-manager.js.map +1 -0
  69. package/dist/notebook-creation/test-create.d.ts +8 -0
  70. package/dist/notebook-creation/test-create.d.ts.map +1 -0
  71. package/dist/notebook-creation/test-create.js +72 -0
  72. package/dist/notebook-creation/test-create.js.map +1 -0
  73. package/dist/notebook-creation/types.d.ts +173 -0
  74. package/dist/notebook-creation/types.d.ts.map +1 -0
  75. package/dist/notebook-creation/types.js +5 -0
  76. package/dist/notebook-creation/types.js.map +1 -0
  77. package/dist/quota/index.d.ts +8 -0
  78. package/dist/quota/index.d.ts.map +1 -0
  79. package/dist/quota/index.js +8 -0
  80. package/dist/quota/index.js.map +1 -0
  81. package/dist/quota/quota-manager.d.ts +125 -0
  82. package/dist/quota/quota-manager.d.ts.map +1 -0
  83. package/dist/quota/quota-manager.js +330 -0
  84. package/dist/quota/quota-manager.js.map +1 -0
  85. package/dist/session/session-manager.d.ts +5 -0
  86. package/dist/session/session-manager.d.ts.map +1 -1
  87. package/dist/session/session-manager.js +6 -0
  88. package/dist/session/session-manager.js.map +1 -1
  89. package/dist/tools/definitions/notebook-management.d.ts.map +1 -1
  90. package/dist/tools/definitions/notebook-management.js +525 -0
  91. package/dist/tools/definitions/notebook-management.js.map +1 -1
  92. package/dist/tools/definitions/system.d.ts.map +1 -1
  93. package/dist/tools/definitions/system.js +158 -0
  94. package/dist/tools/definitions/system.js.map +1 -1
  95. package/dist/tools/handlers.d.ts +225 -0
  96. package/dist/tools/handlers.d.ts.map +1 -1
  97. package/dist/tools/handlers.js +911 -0
  98. package/dist/tools/handlers.js.map +1 -1
  99. package/dist/webhooks/index.d.ts +8 -0
  100. package/dist/webhooks/index.d.ts.map +1 -0
  101. package/dist/webhooks/index.js +8 -0
  102. package/dist/webhooks/index.js.map +1 -0
  103. package/dist/webhooks/types.d.ts +57 -0
  104. package/dist/webhooks/types.d.ts.map +1 -0
  105. package/dist/webhooks/types.js +5 -0
  106. package/dist/webhooks/types.js.map +1 -0
  107. package/dist/webhooks/webhook-dispatcher.d.ts +120 -0
  108. package/dist/webhooks/webhook-dispatcher.d.ts.map +1 -0
  109. package/dist/webhooks/webhook-dispatcher.js +519 -0
  110. package/dist/webhooks/webhook-dispatcher.js.map +1 -0
  111. package/package.json +1 -1
@@ -10,6 +10,12 @@ import { validateNotebookUrl, validateNotebookId, validateSessionId, validateQue
10
10
  import { audit } from "../utils/audit-logger.js";
11
11
  import { validateResponse } from "../utils/response-validator.js";
12
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 { getWebhookDispatcher } from "../webhooks/index.js";
18
+ import { getQuotaManager } from "../quota/index.js";
13
19
  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.";
14
20
  /**
15
21
  * MCP Tool Handlers
@@ -71,6 +77,17 @@ export class ToolHandlers {
71
77
  error: `Rate limit exceeded. Please wait before making more requests. Remaining: ${this.rateLimiter.getRemaining(rateLimitKey)}`,
72
78
  };
73
79
  }
80
+ // === QUOTA CHECK ===
81
+ const quotaManager = getQuotaManager();
82
+ const canQuery = quotaManager.canMakeQuery();
83
+ if (!canQuery.allowed) {
84
+ log.warning(`⚠️ Quota limit: ${canQuery.reason}`);
85
+ await audit.tool("ask_question", args, false, Date.now() - startTime, canQuery.reason || "Query quota exceeded");
86
+ return {
87
+ success: false,
88
+ error: canQuery.reason || "Daily query limit reached. Try again tomorrow or upgrade your plan.",
89
+ };
90
+ }
74
91
  }
75
92
  catch (error) {
76
93
  if (error instanceof SecurityError) {
@@ -173,6 +190,8 @@ export class ToolHandlers {
173
190
  // Progress: Complete
174
191
  await sendProgress?.("Question answered successfully!", 5, 5);
175
192
  log.success(`✅ [TOOL] ask_question completed successfully`);
193
+ // Update quota tracking
194
+ getQuotaManager().incrementQueryCount();
176
195
  // Audit: successful tool call
177
196
  await audit.tool("ask_question", {
178
197
  question_length: safeQuestion.length,
@@ -733,6 +752,427 @@ export class ToolHandlers {
733
752
  };
734
753
  }
735
754
  }
755
+ /**
756
+ * Handle export_library tool
757
+ *
758
+ * Exports notebook library to a backup file (JSON or CSV).
759
+ */
760
+ async handleExportLibrary(args) {
761
+ const format = args.format || "json";
762
+ log.info(`🔧 [TOOL] export_library called`);
763
+ log.info(` Format: ${format}`);
764
+ try {
765
+ const notebooks = this.library.listNotebooks();
766
+ const stats = this.library.getStats();
767
+ // Generate default output path if not provided
768
+ const date = new Date().toISOString().split("T")[0];
769
+ const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
770
+ const defaultPath = `${homeDir}/notebooklm-library-backup-${date}.${format}`;
771
+ const outputPath = args.output_path || defaultPath;
772
+ let content;
773
+ if (format === "csv") {
774
+ // CSV format: name, url, topics, last_used, use_count
775
+ const headers = ["name", "url", "topics", "description", "last_used", "use_count"];
776
+ const rows = notebooks.map((nb) => [
777
+ `"${(nb.name || "").replace(/"/g, '""')}"`,
778
+ `"${nb.url}"`,
779
+ `"${(nb.topics || []).join("; ")}"`,
780
+ `"${(nb.description || "").replace(/"/g, '""')}"`,
781
+ nb.last_used || "",
782
+ String(nb.use_count || 0),
783
+ ]);
784
+ content = [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
785
+ }
786
+ else {
787
+ // JSON format: full library data
788
+ content = JSON.stringify({
789
+ exported_at: new Date().toISOString(),
790
+ version: "1.0",
791
+ stats: {
792
+ total_notebooks: stats.total_notebooks,
793
+ total_queries: stats.total_queries,
794
+ },
795
+ notebooks: notebooks,
796
+ }, null, 2);
797
+ }
798
+ // Write file with secure permissions
799
+ const fs = await import("fs");
800
+ fs.writeFileSync(outputPath, content, { mode: 0o600 });
801
+ const fileStats = fs.statSync(outputPath);
802
+ log.success(`✅ [TOOL] export_library completed: ${outputPath}`);
803
+ return {
804
+ success: true,
805
+ data: {
806
+ file_path: outputPath,
807
+ format,
808
+ notebook_count: notebooks.length,
809
+ size_bytes: fileStats.size,
810
+ },
811
+ };
812
+ }
813
+ catch (error) {
814
+ const errorMessage = error instanceof Error ? error.message : String(error);
815
+ log.error(`❌ [TOOL] export_library failed: ${errorMessage}`);
816
+ return {
817
+ success: false,
818
+ error: errorMessage,
819
+ };
820
+ }
821
+ }
822
+ /**
823
+ * Handle get_project_info tool
824
+ *
825
+ * Returns current project context and library location.
826
+ */
827
+ async handleGetProjectInfo() {
828
+ log.info(`🔧 [TOOL] get_project_info called`);
829
+ try {
830
+ // Get info from the library instance
831
+ const projectInfo = this.library.getProjectInfo();
832
+ const libraryPath = this.library.getLibraryPath();
833
+ const isProjectLibrary = this.library.isProjectLibrary();
834
+ // Also detect what project would be detected from cwd
835
+ const { NotebookLibrary: NL } = await import("../library/notebook-library.js");
836
+ const detectedProject = NL.detectCurrentProject();
837
+ log.success(`✅ [TOOL] get_project_info completed`);
838
+ return {
839
+ success: true,
840
+ data: {
841
+ project: projectInfo,
842
+ library_path: libraryPath,
843
+ is_project_library: isProjectLibrary,
844
+ detected_project: detectedProject,
845
+ },
846
+ };
847
+ }
848
+ catch (error) {
849
+ const errorMessage = error instanceof Error ? error.message : String(error);
850
+ log.error(`❌ [TOOL] get_project_info failed: ${errorMessage}`);
851
+ return {
852
+ success: false,
853
+ error: errorMessage,
854
+ };
855
+ }
856
+ }
857
+ /**
858
+ * Handle get_quota tool
859
+ *
860
+ * Returns current quota status including license tier, usage, and limits.
861
+ */
862
+ async handleGetQuota() {
863
+ log.info(`🔧 [TOOL] get_quota called`);
864
+ try {
865
+ const quotaManager = getQuotaManager();
866
+ const status = quotaManager.getStatus();
867
+ const settings = quotaManager.getSettings();
868
+ log.success(`✅ [TOOL] get_quota completed (tier: ${status.tier})`);
869
+ return {
870
+ success: true,
871
+ data: {
872
+ tier: status.tier,
873
+ notebooks: status.notebooks,
874
+ sources: status.sources,
875
+ queries: status.queries,
876
+ auto_detected: settings.autoDetected,
877
+ last_updated: settings.usage.lastUpdated,
878
+ },
879
+ };
880
+ }
881
+ catch (error) {
882
+ const errorMessage = error instanceof Error ? error.message : String(error);
883
+ log.error(`❌ [TOOL] get_quota failed: ${errorMessage}`);
884
+ return {
885
+ success: false,
886
+ error: errorMessage,
887
+ };
888
+ }
889
+ }
890
+ /**
891
+ * Handle set_quota_tier tool
892
+ *
893
+ * Manually set the license tier to override auto-detection.
894
+ */
895
+ async handleSetQuotaTier(args) {
896
+ log.info(`🔧 [TOOL] set_quota_tier called`);
897
+ log.info(` Tier: ${args.tier}`);
898
+ try {
899
+ const quotaManager = getQuotaManager();
900
+ quotaManager.setTier(args.tier);
901
+ const settings = quotaManager.getSettings();
902
+ log.success(`✅ [TOOL] set_quota_tier completed (tier: ${args.tier})`);
903
+ return {
904
+ success: true,
905
+ data: {
906
+ tier: settings.tier,
907
+ limits: {
908
+ notebooks: settings.limits.notebooks,
909
+ sourcesPerNotebook: settings.limits.sourcesPerNotebook,
910
+ queriesPerDay: settings.limits.queriesPerDay,
911
+ },
912
+ message: `License tier set to ${args.tier}. Limits updated accordingly.`,
913
+ },
914
+ };
915
+ }
916
+ catch (error) {
917
+ const errorMessage = error instanceof Error ? error.message : String(error);
918
+ log.error(`❌ [TOOL] set_quota_tier failed: ${errorMessage}`);
919
+ return {
920
+ success: false,
921
+ error: errorMessage,
922
+ };
923
+ }
924
+ }
925
+ /**
926
+ * Handle create_notebook tool
927
+ *
928
+ * Creates a new NotebookLM notebook with sources programmatically.
929
+ */
930
+ async handleCreateNotebook(args, sendProgress) {
931
+ log.info(`🔧 [TOOL] create_notebook called`);
932
+ log.info(` Name: ${args.name}`);
933
+ log.info(` Sources: ${args.sources?.length || 0}`);
934
+ try {
935
+ // Validate inputs
936
+ if (!args.name || typeof args.name !== "string") {
937
+ throw new Error("Notebook name is required");
938
+ }
939
+ if (!args.sources || !Array.isArray(args.sources) || args.sources.length === 0) {
940
+ throw new Error("At least one source is required");
941
+ }
942
+ // Validate each source
943
+ for (const source of args.sources) {
944
+ if (!source.type || !["url", "text", "file"].includes(source.type)) {
945
+ throw new Error(`Invalid source type: ${source.type}. Must be url, text, or file.`);
946
+ }
947
+ if (!source.value || typeof source.value !== "string") {
948
+ throw new Error("Source value is required");
949
+ }
950
+ if (source.type === "url") {
951
+ try {
952
+ new URL(source.value);
953
+ }
954
+ catch {
955
+ throw new Error(`Invalid URL: ${source.value}`);
956
+ }
957
+ }
958
+ }
959
+ // === QUOTA CHECK ===
960
+ const quotaManager = getQuotaManager();
961
+ const canCreate = quotaManager.canCreateNotebook();
962
+ if (!canCreate.allowed) {
963
+ log.warning(`⚠️ Quota limit: ${canCreate.reason}`);
964
+ return {
965
+ success: false,
966
+ error: canCreate.reason || "Notebook quota limit reached",
967
+ };
968
+ }
969
+ // Check source limit
970
+ const sourceLimits = quotaManager.getLimits();
971
+ if (args.sources.length > sourceLimits.sourcesPerNotebook) {
972
+ const reason = `Too many sources (${args.sources.length}). Limit is ${sourceLimits.sourcesPerNotebook} per notebook.`;
973
+ log.warning(`⚠️ Quota limit: ${reason}`);
974
+ return {
975
+ success: false,
976
+ error: reason,
977
+ };
978
+ }
979
+ // Get the shared context manager from session manager
980
+ const contextManager = this.sessionManager.getContextManager();
981
+ // Create notebook
982
+ const creator = new NotebookCreator(this.authManager, contextManager);
983
+ const result = await creator.createNotebook({
984
+ name: args.name,
985
+ sources: args.sources,
986
+ sendProgress,
987
+ browserOptions: args.browser_options || (args.show_browser ? { show: true } : undefined),
988
+ });
989
+ // Auto-add to library if requested (default: true)
990
+ if (args.auto_add_to_library !== false) {
991
+ try {
992
+ this.library.addNotebook({
993
+ url: result.url,
994
+ name: args.name,
995
+ description: args.description || `Created ${new Date().toLocaleDateString()}`,
996
+ topics: args.topics || [],
997
+ });
998
+ log.success(`✅ Added notebook to library: ${args.name}`);
999
+ }
1000
+ catch (libError) {
1001
+ log.warning(`⚠️ Failed to add to library: ${libError}`);
1002
+ // Don't fail the whole operation
1003
+ }
1004
+ }
1005
+ // Update quota tracking
1006
+ quotaManager.incrementNotebookCount();
1007
+ // Audit log
1008
+ await audit.tool("create_notebook", {
1009
+ name: args.name,
1010
+ sourceCount: args.sources.length,
1011
+ url: result.url,
1012
+ }, true, 0);
1013
+ log.success(`✅ [TOOL] create_notebook completed: ${result.url}`);
1014
+ return {
1015
+ success: true,
1016
+ data: result,
1017
+ };
1018
+ }
1019
+ catch (error) {
1020
+ const errorMessage = error instanceof Error ? error.message : String(error);
1021
+ log.error(`❌ [TOOL] create_notebook failed: ${errorMessage}`);
1022
+ await audit.tool("create_notebook", {
1023
+ name: args.name,
1024
+ error: errorMessage,
1025
+ }, false, 0, errorMessage);
1026
+ return {
1027
+ success: false,
1028
+ error: errorMessage,
1029
+ };
1030
+ }
1031
+ }
1032
+ /**
1033
+ * Handle batch_create_notebooks tool
1034
+ *
1035
+ * Creates multiple notebooks in a single batch operation.
1036
+ */
1037
+ async handleBatchCreateNotebooks(args, sendProgress) {
1038
+ log.info(`🔧 [TOOL] batch_create_notebooks called`);
1039
+ log.info(` Notebooks: ${args.notebooks.length}`);
1040
+ log.info(` Stop on error: ${args.stop_on_error || false}`);
1041
+ try {
1042
+ // Validate input
1043
+ if (!args.notebooks || !Array.isArray(args.notebooks)) {
1044
+ throw new Error("notebooks array is required");
1045
+ }
1046
+ if (args.notebooks.length === 0) {
1047
+ throw new Error("At least one notebook is required");
1048
+ }
1049
+ if (args.notebooks.length > 10) {
1050
+ throw new Error("Maximum 10 notebooks per batch");
1051
+ }
1052
+ const results = [];
1053
+ const total = args.notebooks.length;
1054
+ let succeeded = 0;
1055
+ let failed = 0;
1056
+ for (let i = 0; i < args.notebooks.length; i++) {
1057
+ const notebook = args.notebooks[i];
1058
+ await sendProgress?.(`Creating notebook ${i + 1}/${total}: ${notebook.name}`, i, total);
1059
+ log.info(` 📓 Creating notebook ${i + 1}/${total}: ${notebook.name}`);
1060
+ try {
1061
+ const result = await this.handleCreateNotebook({
1062
+ name: notebook.name,
1063
+ sources: notebook.sources,
1064
+ description: notebook.description,
1065
+ topics: notebook.topics,
1066
+ auto_add_to_library: true,
1067
+ show_browser: args.show_browser,
1068
+ });
1069
+ if (result.success && result.data) {
1070
+ results.push({
1071
+ name: notebook.name,
1072
+ success: true,
1073
+ url: result.data.url,
1074
+ });
1075
+ succeeded++;
1076
+ log.success(` ✅ Created: ${result.data.url}`);
1077
+ }
1078
+ else {
1079
+ results.push({
1080
+ name: notebook.name,
1081
+ success: false,
1082
+ error: result.error || "Unknown error",
1083
+ });
1084
+ failed++;
1085
+ log.error(` ❌ Failed: ${result.error}`);
1086
+ if (args.stop_on_error) {
1087
+ log.warning(` ⚠️ Stopping batch due to error (stop_on_error=true)`);
1088
+ break;
1089
+ }
1090
+ }
1091
+ }
1092
+ catch (error) {
1093
+ const errorMessage = error instanceof Error ? error.message : String(error);
1094
+ results.push({
1095
+ name: notebook.name,
1096
+ success: false,
1097
+ error: errorMessage,
1098
+ });
1099
+ failed++;
1100
+ log.error(` ❌ Exception: ${errorMessage}`);
1101
+ if (args.stop_on_error) {
1102
+ log.warning(` ⚠️ Stopping batch due to exception (stop_on_error=true)`);
1103
+ break;
1104
+ }
1105
+ }
1106
+ // Delay between notebooks to avoid rate limiting
1107
+ if (i < args.notebooks.length - 1) {
1108
+ const delay = 2000 + Math.random() * 2000; // 2-4 seconds
1109
+ await new Promise((resolve) => setTimeout(resolve, delay));
1110
+ }
1111
+ }
1112
+ await sendProgress?.(`Batch complete: ${succeeded}/${total} succeeded`, total, total);
1113
+ log.success(`✅ [TOOL] batch_create_notebooks completed: ${succeeded}/${total} succeeded`);
1114
+ return {
1115
+ success: failed === 0,
1116
+ data: {
1117
+ total,
1118
+ succeeded,
1119
+ failed,
1120
+ results,
1121
+ },
1122
+ };
1123
+ }
1124
+ catch (error) {
1125
+ const errorMessage = error instanceof Error ? error.message : String(error);
1126
+ log.error(`❌ [TOOL] batch_create_notebooks failed: ${errorMessage}`);
1127
+ return {
1128
+ success: false,
1129
+ error: errorMessage,
1130
+ };
1131
+ }
1132
+ }
1133
+ /**
1134
+ * Handle sync_library tool
1135
+ *
1136
+ * Syncs local library with actual NotebookLM notebooks.
1137
+ */
1138
+ async handleSyncLibrary(args) {
1139
+ log.info(`🔧 [TOOL] sync_library called`);
1140
+ log.info(` Auto-fix: ${args.auto_fix || false}`);
1141
+ log.info(` Show browser: ${args.show_browser || false}`);
1142
+ try {
1143
+ // Get the shared context manager from session manager
1144
+ const contextManager = this.sessionManager.getContextManager();
1145
+ // Sync library
1146
+ const sync = new NotebookSync(this.authManager, contextManager, this.library);
1147
+ const result = await sync.syncLibrary({
1148
+ autoFix: args.auto_fix,
1149
+ showBrowser: args.show_browser,
1150
+ });
1151
+ // Audit log
1152
+ await audit.tool("sync_library", {
1153
+ matched: result.matched.length,
1154
+ stale: result.staleEntries.length,
1155
+ missing: result.missingNotebooks.length,
1156
+ autoFix: args.auto_fix,
1157
+ }, true, 0);
1158
+ log.success(`✅ [TOOL] sync_library completed`);
1159
+ return {
1160
+ success: true,
1161
+ data: result,
1162
+ };
1163
+ }
1164
+ catch (error) {
1165
+ const errorMessage = error instanceof Error ? error.message : String(error);
1166
+ log.error(`❌ [TOOL] sync_library failed: ${errorMessage}`);
1167
+ await audit.tool("sync_library", {
1168
+ error: errorMessage,
1169
+ }, false, 0, errorMessage);
1170
+ return {
1171
+ success: false,
1172
+ error: errorMessage,
1173
+ };
1174
+ }
1175
+ }
736
1176
  /**
737
1177
  * Handle cleanup_data tool
738
1178
  *
@@ -801,6 +1241,477 @@ export class ToolHandlers {
801
1241
  };
802
1242
  }
803
1243
  }
1244
+ /**
1245
+ * Handle list_sources tool
1246
+ *
1247
+ * List all sources in a NotebookLM notebook.
1248
+ */
1249
+ async handleListSources(args) {
1250
+ log.info(`🔧 [TOOL] list_sources called`);
1251
+ try {
1252
+ // Resolve notebook URL
1253
+ let notebookUrl = args.notebook_url;
1254
+ if (!notebookUrl && args.notebook_id) {
1255
+ const notebook = this.library.getNotebook(args.notebook_id);
1256
+ if (!notebook) {
1257
+ throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1258
+ }
1259
+ notebookUrl = notebook.url;
1260
+ log.info(` Resolved notebook: ${notebook.name}`);
1261
+ }
1262
+ else if (!notebookUrl) {
1263
+ const active = this.library.getActiveNotebook();
1264
+ if (active) {
1265
+ notebookUrl = active.url;
1266
+ log.info(` Using active notebook: ${active.name}`);
1267
+ }
1268
+ else {
1269
+ throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1270
+ }
1271
+ }
1272
+ // Validate URL
1273
+ const safeUrl = validateNotebookUrl(notebookUrl);
1274
+ // Get the shared context manager from session manager
1275
+ const contextManager = this.sessionManager.getContextManager();
1276
+ // List sources
1277
+ const sourceManager = new SourceManager(this.authManager, contextManager);
1278
+ const result = await sourceManager.listSources(safeUrl);
1279
+ log.success(`✅ [TOOL] list_sources completed (${result.count} sources)`);
1280
+ return {
1281
+ success: true,
1282
+ data: result,
1283
+ };
1284
+ }
1285
+ catch (error) {
1286
+ const errorMessage = error instanceof Error ? error.message : String(error);
1287
+ log.error(`❌ [TOOL] list_sources failed: ${errorMessage}`);
1288
+ return {
1289
+ success: false,
1290
+ error: errorMessage,
1291
+ };
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Handle add_source tool
1296
+ *
1297
+ * Add a source to an existing NotebookLM notebook.
1298
+ */
1299
+ async handleAddSource(args) {
1300
+ log.info(`🔧 [TOOL] add_source called`);
1301
+ log.info(` Source type: ${args.source?.type}`);
1302
+ try {
1303
+ // Validate source
1304
+ if (!args.source || !args.source.type || !args.source.value) {
1305
+ throw new Error("Source with type and value is required");
1306
+ }
1307
+ if (!["url", "text", "file"].includes(args.source.type)) {
1308
+ throw new Error(`Invalid source type: ${args.source.type}. Must be url, text, or file.`);
1309
+ }
1310
+ // Resolve notebook URL
1311
+ let notebookUrl = args.notebook_url;
1312
+ if (!notebookUrl && args.notebook_id) {
1313
+ const notebook = this.library.getNotebook(args.notebook_id);
1314
+ if (!notebook) {
1315
+ throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1316
+ }
1317
+ notebookUrl = notebook.url;
1318
+ log.info(` Resolved notebook: ${notebook.name}`);
1319
+ }
1320
+ else if (!notebookUrl) {
1321
+ const active = this.library.getActiveNotebook();
1322
+ if (active) {
1323
+ notebookUrl = active.url;
1324
+ log.info(` Using active notebook: ${active.name}`);
1325
+ }
1326
+ else {
1327
+ throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1328
+ }
1329
+ }
1330
+ // Validate URL
1331
+ const safeUrl = validateNotebookUrl(notebookUrl);
1332
+ // Get the shared context manager from session manager
1333
+ const contextManager = this.sessionManager.getContextManager();
1334
+ // Add source
1335
+ const sourceManager = new SourceManager(this.authManager, contextManager);
1336
+ const result = await sourceManager.addSource(safeUrl, args.source);
1337
+ if (result.success) {
1338
+ log.success(`✅ [TOOL] add_source completed`);
1339
+ }
1340
+ else {
1341
+ log.warning(`⚠️ [TOOL] add_source failed: ${result.error}`);
1342
+ }
1343
+ return {
1344
+ success: result.success,
1345
+ data: result,
1346
+ ...(result.error && { error: result.error }),
1347
+ };
1348
+ }
1349
+ catch (error) {
1350
+ const errorMessage = error instanceof Error ? error.message : String(error);
1351
+ log.error(`❌ [TOOL] add_source failed: ${errorMessage}`);
1352
+ return {
1353
+ success: false,
1354
+ error: errorMessage,
1355
+ };
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Handle remove_source tool
1360
+ *
1361
+ * Remove a source from a NotebookLM notebook.
1362
+ */
1363
+ async handleRemoveSource(args) {
1364
+ log.info(`🔧 [TOOL] remove_source called`);
1365
+ log.info(` Source ID: ${args.source_id}`);
1366
+ try {
1367
+ // Validate source_id
1368
+ if (!args.source_id) {
1369
+ throw new Error("source_id is required");
1370
+ }
1371
+ // Resolve notebook URL
1372
+ let notebookUrl = args.notebook_url;
1373
+ if (!notebookUrl && args.notebook_id) {
1374
+ const notebook = this.library.getNotebook(args.notebook_id);
1375
+ if (!notebook) {
1376
+ throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1377
+ }
1378
+ notebookUrl = notebook.url;
1379
+ log.info(` Resolved notebook: ${notebook.name}`);
1380
+ }
1381
+ else if (!notebookUrl) {
1382
+ const active = this.library.getActiveNotebook();
1383
+ if (active) {
1384
+ notebookUrl = active.url;
1385
+ log.info(` Using active notebook: ${active.name}`);
1386
+ }
1387
+ else {
1388
+ throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1389
+ }
1390
+ }
1391
+ // Validate URL
1392
+ const safeUrl = validateNotebookUrl(notebookUrl);
1393
+ // Get the shared context manager from session manager
1394
+ const contextManager = this.sessionManager.getContextManager();
1395
+ // Remove source
1396
+ const sourceManager = new SourceManager(this.authManager, contextManager);
1397
+ const result = await sourceManager.removeSource(safeUrl, args.source_id);
1398
+ if (result.success) {
1399
+ log.success(`✅ [TOOL] remove_source completed`);
1400
+ }
1401
+ else {
1402
+ log.warning(`⚠️ [TOOL] remove_source failed: ${result.error}`);
1403
+ }
1404
+ return {
1405
+ success: result.success,
1406
+ data: result,
1407
+ ...(result.error && { error: result.error }),
1408
+ };
1409
+ }
1410
+ catch (error) {
1411
+ const errorMessage = error instanceof Error ? error.message : String(error);
1412
+ log.error(`❌ [TOOL] remove_source failed: ${errorMessage}`);
1413
+ return {
1414
+ success: false,
1415
+ error: errorMessage,
1416
+ };
1417
+ }
1418
+ }
1419
+ /**
1420
+ * Handle generate_audio_overview tool
1421
+ *
1422
+ * Triggers audio overview generation for a notebook.
1423
+ */
1424
+ async handleGenerateAudioOverview(args) {
1425
+ log.info(`🔧 [TOOL] generate_audio_overview called`);
1426
+ try {
1427
+ // Resolve notebook URL
1428
+ let notebookUrl = args.notebook_url;
1429
+ if (!notebookUrl && args.notebook_id) {
1430
+ const notebook = this.library.getNotebook(args.notebook_id);
1431
+ if (!notebook) {
1432
+ throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1433
+ }
1434
+ notebookUrl = notebook.url;
1435
+ log.info(` Resolved notebook: ${notebook.name}`);
1436
+ }
1437
+ else if (!notebookUrl) {
1438
+ const active = this.library.getActiveNotebook();
1439
+ if (active) {
1440
+ notebookUrl = active.url;
1441
+ log.info(` Using active notebook: ${active.name}`);
1442
+ }
1443
+ else {
1444
+ throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1445
+ }
1446
+ }
1447
+ // Validate URL
1448
+ const safeUrl = validateNotebookUrl(notebookUrl);
1449
+ // Get the shared context manager from session manager
1450
+ const contextManager = this.sessionManager.getContextManager();
1451
+ // Generate audio
1452
+ const audioManager = new AudioManager(this.authManager, contextManager);
1453
+ const result = await audioManager.generateAudioOverview(safeUrl);
1454
+ if (result.success) {
1455
+ log.success(`✅ [TOOL] generate_audio_overview completed (status: ${result.status.status})`);
1456
+ }
1457
+ else {
1458
+ log.warning(`⚠️ [TOOL] generate_audio_overview: ${result.error}`);
1459
+ }
1460
+ return {
1461
+ success: result.success,
1462
+ data: result,
1463
+ ...(result.error && { error: result.error }),
1464
+ };
1465
+ }
1466
+ catch (error) {
1467
+ const errorMessage = error instanceof Error ? error.message : String(error);
1468
+ log.error(`❌ [TOOL] generate_audio_overview failed: ${errorMessage}`);
1469
+ return {
1470
+ success: false,
1471
+ error: errorMessage,
1472
+ };
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Handle get_audio_status tool
1477
+ *
1478
+ * Checks the audio generation status for a notebook.
1479
+ */
1480
+ async handleGetAudioStatus(args) {
1481
+ log.info(`🔧 [TOOL] get_audio_status called`);
1482
+ try {
1483
+ // Resolve notebook URL
1484
+ let notebookUrl = args.notebook_url;
1485
+ if (!notebookUrl && args.notebook_id) {
1486
+ const notebook = this.library.getNotebook(args.notebook_id);
1487
+ if (!notebook) {
1488
+ throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1489
+ }
1490
+ notebookUrl = notebook.url;
1491
+ log.info(` Resolved notebook: ${notebook.name}`);
1492
+ }
1493
+ else if (!notebookUrl) {
1494
+ const active = this.library.getActiveNotebook();
1495
+ if (active) {
1496
+ notebookUrl = active.url;
1497
+ log.info(` Using active notebook: ${active.name}`);
1498
+ }
1499
+ else {
1500
+ throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1501
+ }
1502
+ }
1503
+ // Validate URL
1504
+ const safeUrl = validateNotebookUrl(notebookUrl);
1505
+ // Get the shared context manager from session manager
1506
+ const contextManager = this.sessionManager.getContextManager();
1507
+ // Get status
1508
+ const audioManager = new AudioManager(this.authManager, contextManager);
1509
+ const status = await audioManager.getAudioStatus(safeUrl);
1510
+ log.success(`✅ [TOOL] get_audio_status completed (status: ${status.status})`);
1511
+ return {
1512
+ success: true,
1513
+ data: status,
1514
+ };
1515
+ }
1516
+ catch (error) {
1517
+ const errorMessage = error instanceof Error ? error.message : String(error);
1518
+ log.error(`❌ [TOOL] get_audio_status failed: ${errorMessage}`);
1519
+ return {
1520
+ success: false,
1521
+ error: errorMessage,
1522
+ };
1523
+ }
1524
+ }
1525
+ /**
1526
+ * Handle download_audio tool
1527
+ *
1528
+ * Downloads the generated audio file.
1529
+ */
1530
+ async handleDownloadAudio(args) {
1531
+ log.info(`🔧 [TOOL] download_audio called`);
1532
+ try {
1533
+ // Resolve notebook URL
1534
+ let notebookUrl = args.notebook_url;
1535
+ if (!notebookUrl && args.notebook_id) {
1536
+ const notebook = this.library.getNotebook(args.notebook_id);
1537
+ if (!notebook) {
1538
+ throw new Error(`Notebook not found in library: ${args.notebook_id}`);
1539
+ }
1540
+ notebookUrl = notebook.url;
1541
+ log.info(` Resolved notebook: ${notebook.name}`);
1542
+ }
1543
+ else if (!notebookUrl) {
1544
+ const active = this.library.getActiveNotebook();
1545
+ if (active) {
1546
+ notebookUrl = active.url;
1547
+ log.info(` Using active notebook: ${active.name}`);
1548
+ }
1549
+ else {
1550
+ throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
1551
+ }
1552
+ }
1553
+ // Validate URL
1554
+ const safeUrl = validateNotebookUrl(notebookUrl);
1555
+ // Get the shared context manager from session manager
1556
+ const contextManager = this.sessionManager.getContextManager();
1557
+ // Download audio
1558
+ const audioManager = new AudioManager(this.authManager, contextManager);
1559
+ const result = await audioManager.downloadAudio(safeUrl, args.output_path);
1560
+ if (result.success) {
1561
+ log.success(`✅ [TOOL] download_audio completed: ${result.filePath}`);
1562
+ }
1563
+ else {
1564
+ log.warning(`⚠️ [TOOL] download_audio: ${result.error}`);
1565
+ }
1566
+ return {
1567
+ success: result.success,
1568
+ data: result,
1569
+ ...(result.error && { error: result.error }),
1570
+ };
1571
+ }
1572
+ catch (error) {
1573
+ const errorMessage = error instanceof Error ? error.message : String(error);
1574
+ log.error(`❌ [TOOL] download_audio failed: ${errorMessage}`);
1575
+ return {
1576
+ success: false,
1577
+ error: errorMessage,
1578
+ };
1579
+ }
1580
+ }
1581
+ /**
1582
+ * Handle configure_webhook tool
1583
+ *
1584
+ * Add or update a webhook endpoint.
1585
+ */
1586
+ async handleConfigureWebhook(args) {
1587
+ log.info(`🔧 [TOOL] configure_webhook called`);
1588
+ log.info(` Name: ${args.name}`);
1589
+ try {
1590
+ const dispatcher = getWebhookDispatcher();
1591
+ if (args.id) {
1592
+ // Update existing
1593
+ const updated = dispatcher.updateWebhook({
1594
+ id: args.id,
1595
+ name: args.name,
1596
+ url: args.url,
1597
+ enabled: args.enabled,
1598
+ events: args.events,
1599
+ format: args.format,
1600
+ secret: args.secret,
1601
+ });
1602
+ if (!updated) {
1603
+ throw new Error(`Webhook not found: ${args.id}`);
1604
+ }
1605
+ log.success(`✅ [TOOL] configure_webhook updated: ${updated.name}`);
1606
+ return { success: true, data: updated };
1607
+ }
1608
+ else {
1609
+ // Create new
1610
+ const webhook = dispatcher.addWebhook({
1611
+ name: args.name,
1612
+ url: args.url,
1613
+ events: args.events,
1614
+ format: args.format,
1615
+ secret: args.secret,
1616
+ });
1617
+ log.success(`✅ [TOOL] configure_webhook created: ${webhook.name}`);
1618
+ return { success: true, data: webhook };
1619
+ }
1620
+ }
1621
+ catch (error) {
1622
+ const errorMessage = error instanceof Error ? error.message : String(error);
1623
+ log.error(`❌ [TOOL] configure_webhook failed: ${errorMessage}`);
1624
+ return { success: false, error: errorMessage };
1625
+ }
1626
+ }
1627
+ /**
1628
+ * Handle list_webhooks tool
1629
+ *
1630
+ * List all configured webhooks.
1631
+ */
1632
+ async handleListWebhooks() {
1633
+ log.info(`🔧 [TOOL] list_webhooks called`);
1634
+ try {
1635
+ const dispatcher = getWebhookDispatcher();
1636
+ const webhooks = dispatcher.listWebhooks();
1637
+ const stats = dispatcher.getStats();
1638
+ log.success(`✅ [TOOL] list_webhooks completed (${webhooks.length} webhooks)`);
1639
+ return {
1640
+ success: true,
1641
+ data: { webhooks, stats },
1642
+ };
1643
+ }
1644
+ catch (error) {
1645
+ const errorMessage = error instanceof Error ? error.message : String(error);
1646
+ log.error(`❌ [TOOL] list_webhooks failed: ${errorMessage}`);
1647
+ return { success: false, error: errorMessage };
1648
+ }
1649
+ }
1650
+ /**
1651
+ * Handle test_webhook tool
1652
+ *
1653
+ * Send a test event to a webhook.
1654
+ */
1655
+ async handleTestWebhook(args) {
1656
+ log.info(`🔧 [TOOL] test_webhook called`);
1657
+ log.info(` ID: ${args.id}`);
1658
+ try {
1659
+ const dispatcher = getWebhookDispatcher();
1660
+ const result = await dispatcher.testWebhook(args.id);
1661
+ if (result.success) {
1662
+ log.success(`✅ [TOOL] test_webhook succeeded`);
1663
+ return {
1664
+ success: true,
1665
+ data: { success: true, message: "Test event delivered successfully" },
1666
+ };
1667
+ }
1668
+ else {
1669
+ log.warning(`⚠️ [TOOL] test_webhook failed: ${result.error}`);
1670
+ return {
1671
+ success: false,
1672
+ data: { success: false, message: result.error || "Test failed" },
1673
+ error: result.error,
1674
+ };
1675
+ }
1676
+ }
1677
+ catch (error) {
1678
+ const errorMessage = error instanceof Error ? error.message : String(error);
1679
+ log.error(`❌ [TOOL] test_webhook failed: ${errorMessage}`);
1680
+ return { success: false, error: errorMessage };
1681
+ }
1682
+ }
1683
+ /**
1684
+ * Handle remove_webhook tool
1685
+ *
1686
+ * Remove a configured webhook.
1687
+ */
1688
+ async handleRemoveWebhook(args) {
1689
+ log.info(`🔧 [TOOL] remove_webhook called`);
1690
+ log.info(` ID: ${args.id}`);
1691
+ try {
1692
+ const dispatcher = getWebhookDispatcher();
1693
+ const removed = dispatcher.removeWebhook(args.id);
1694
+ if (removed) {
1695
+ log.success(`✅ [TOOL] remove_webhook completed`);
1696
+ return {
1697
+ success: true,
1698
+ data: { removed: true, id: args.id },
1699
+ };
1700
+ }
1701
+ else {
1702
+ log.warning(`⚠️ [TOOL] Webhook not found: ${args.id}`);
1703
+ return {
1704
+ success: false,
1705
+ error: `Webhook not found: ${args.id}`,
1706
+ };
1707
+ }
1708
+ }
1709
+ catch (error) {
1710
+ const errorMessage = error instanceof Error ? error.message : String(error);
1711
+ log.error(`❌ [TOOL] remove_webhook failed: ${errorMessage}`);
1712
+ return { success: false, error: errorMessage };
1713
+ }
1714
+ }
804
1715
  /**
805
1716
  * Cleanup all resources (called on server shutdown)
806
1717
  */