notebooklm-mcp-ultimate 2.3.1 → 2.3.2

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.
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Source Management Tool Handlers
3
3
  *
4
- * Implements handlers for source management tools.
4
+ * Browser-only implementations for source management.
5
+ * Only addDriveSource uses API (requires Drive picker).
5
6
  */
6
7
  import type { NotebookLibrary } from '../../library/notebook-library.js';
7
8
  import type { SessionManager } from '../../session/session-manager.js';
8
9
  import type { ProgressCallback } from '../../types.js';
9
- import * as sourceOps from '../../operations/source-operations.js';
10
10
  /**
11
11
  * Dependencies for source handlers
12
12
  */
@@ -24,50 +24,116 @@ export type SourceHandlers = ReturnType<typeof createSourceHandlers>;
24
24
  export declare function createSourceHandlers(deps: SourceHandlerDependencies): {
25
25
  handleListSources: (args: {
26
26
  notebook_id?: string;
27
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult | {
27
+ }, sendProgress?: ProgressCallback) => Promise<{
28
+ success: boolean;
29
+ error: string;
30
+ notebookId?: undefined;
31
+ sources?: undefined;
32
+ method?: undefined;
33
+ } | {
34
+ success: boolean;
35
+ error: string;
36
+ notebookId: string | null;
37
+ sources?: undefined;
38
+ method?: undefined;
39
+ } | {
28
40
  success: boolean;
29
41
  sources: {
30
42
  title: string;
31
43
  id: string | undefined;
32
44
  }[];
33
45
  method: string;
46
+ notebookId: string | null;
47
+ error?: undefined;
34
48
  }>;
35
49
  handleAddURLSource: (args: {
36
50
  notebook_id?: string;
37
51
  url: string;
38
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult | {
52
+ }, sendProgress?: ProgressCallback) => Promise<{
39
53
  success: boolean;
40
- notebookId: string;
54
+ error: string;
55
+ notebookId: string | null;
56
+ method?: undefined;
57
+ } | {
58
+ success: boolean;
59
+ notebookId: string | null;
41
60
  method: string;
61
+ error?: undefined;
42
62
  }>;
43
63
  handleAddTextSource: (args: {
44
64
  notebook_id?: string;
45
65
  title: string;
46
66
  content: string;
47
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult | {
67
+ }, sendProgress?: ProgressCallback) => Promise<{
68
+ success: boolean;
69
+ error: string;
70
+ notebookId: string | null;
71
+ method?: undefined;
72
+ } | {
48
73
  success: boolean;
49
- notebookId: string;
74
+ notebookId: string | null;
50
75
  method: string;
76
+ error?: undefined;
51
77
  }>;
52
78
  handleAddYouTubeSource: (args: {
53
79
  notebook_id?: string;
54
80
  youtube_url: string;
55
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult | {
81
+ }, sendProgress?: ProgressCallback) => Promise<{
56
82
  success: boolean;
57
- notebookId: string;
83
+ error: string;
84
+ notebookId: string | null;
85
+ method?: undefined;
86
+ } | {
87
+ success: boolean;
88
+ notebookId: string | null;
58
89
  method: string;
90
+ error?: undefined;
59
91
  }>;
60
92
  handleAddDriveSource: (args: {
61
93
  notebook_id?: string;
62
94
  file_id: string;
63
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult>;
95
+ }, sendProgress?: ProgressCallback) => Promise<{
96
+ notebookId: string | null;
97
+ success: boolean;
98
+ sources?: import("../../api/types.js").APISource[];
99
+ source?: import("../../api/types.js").APISource;
100
+ sourceId?: string;
101
+ error?: string;
102
+ }>;
64
103
  handleDeleteSource: (args: {
65
104
  notebook_id?: string;
66
105
  source_id: string;
67
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult>;
106
+ }, sendProgress?: ProgressCallback) => Promise<{
107
+ success: boolean;
108
+ error: string;
109
+ notebookId: string | undefined;
110
+ method?: undefined;
111
+ } | {
112
+ success: boolean;
113
+ notebookId: string | undefined;
114
+ method: string;
115
+ error?: undefined;
116
+ }>;
68
117
  handleSummarizeSource: (args: {
69
118
  notebook_id?: string;
70
119
  source_id: string;
71
- }, sendProgress?: ProgressCallback) => Promise<sourceOps.SourceOperationResult>;
120
+ }, sendProgress?: ProgressCallback) => Promise<{
121
+ success: boolean;
122
+ error: string;
123
+ notebookId: string | undefined;
124
+ source?: undefined;
125
+ method?: undefined;
126
+ } | {
127
+ success: boolean;
128
+ source: {
129
+ title: string;
130
+ type?: string;
131
+ addedDate?: string;
132
+ summary?: string;
133
+ };
134
+ notebookId: string | undefined;
135
+ method: string;
136
+ error?: undefined;
137
+ }>;
72
138
  };
73
139
  //# sourceMappingURL=source-handlers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-handlers.d.ts","sourceRoot":"","sources":["../../../src/tools/handlers/source-handlers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,SAAS,MAAM,uCAAuC,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,cAAc,CAAC,EAAE,cAAc,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAErE;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,yBAAyB;8BA+BtD;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,iBACf,gBAAgB;;;;;;;;+BAyDzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,iBAC5B,gBAAgB;;;;;gCAoDzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,iBAC/C,gBAAgB;;;;;mCAwDzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,iBACpC,gBAAgB;;;;;iCAoDzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,iBAChC,gBAAgB;+BA2BzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,iBAClC,gBAAgB;kCA2BzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,iBAClC,gBAAgB;EAgCtC"}
1
+ {"version":3,"file":"source-handlers.d.ts","sourceRoot":"","sources":["../../../src/tools/handlers/source-handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIvD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,cAAc,CAAC,EAAE,cAAc,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAErE;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,yBAAyB;8BAwEtD;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,iBACf,gBAAgB;;;;;;;;;;;;;;;;;;;;;;+BA6CzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,iBAC5B,gBAAgB;;;;;;;;;;;gCAmCzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,iBAC/C,gBAAgB;;;;;;;;;;;mCAmCzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,iBACpC,gBAAgB;;;;;;;;;;;iCAmCzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,iBAChC,gBAAgB;;;;;;;;+BAyBzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,iBAClC,gBAAgB;;;;;;;;;;;kCA6CzB;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,iBAClC,gBAAgB;;;;;;;;;;;;;;;;;;EAwDtC"}
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Source Management Tool Handlers
3
3
  *
4
- * Implements handlers for source management tools.
4
+ * Browser-only implementations for source management.
5
+ * Only addDriveSource uses API (requires Drive picker).
5
6
  */
6
7
  import { log } from '../../utils/logger.js';
7
8
  import * as sourceOps from '../../operations/source-operations.js';
@@ -11,261 +12,293 @@ import * as sourceOps from '../../operations/source-operations.js';
11
12
  export function createSourceHandlers(deps) {
12
13
  const { library, sessionManager } = deps;
13
14
  /**
14
- * Build notebook URL from ID
15
+ * UUID regex pattern
15
16
  */
16
- function buildNotebookUrl(notebookId) {
17
- return `https://notebooklm.google.com/notebook/${notebookId}`;
17
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18
+ /**
19
+ * Extract notebook UUID from URL
20
+ */
21
+ function extractUuidFromUrl(url) {
22
+ const match = url.match(/\/notebook\/([0-9a-f-]{36})/i);
23
+ return match ? match[1] : null;
18
24
  }
19
25
  /**
20
- * Get notebook ID, using active notebook if not specified
26
+ * Resolve notebook to full URL (browser needs full URL)
21
27
  */
22
- function resolveNotebookId(notebookId) {
28
+ function resolveNotebookUrl(notebookId) {
29
+ // If UUID provided directly, build URL
30
+ if (notebookId && UUID_PATTERN.test(notebookId)) {
31
+ return `https://notebooklm.google.com/notebook/${notebookId}`;
32
+ }
33
+ // Get notebook from library (by alias or active)
34
+ const notebook = notebookId
35
+ ? library.getNotebook(notebookId)
36
+ : library.getActiveNotebook();
37
+ if (!notebook) {
38
+ log.warning('No notebook specified and no active notebook selected');
39
+ return null;
40
+ }
41
+ return notebook.url;
42
+ }
43
+ /**
44
+ * Get notebook alias for user-facing responses
45
+ */
46
+ function resolveNotebookAlias(notebookId) {
23
47
  if (notebookId) {
48
+ const notebook = library.getNotebook(notebookId);
49
+ return notebook?.id || notebookId;
50
+ }
51
+ return library.getActiveNotebook()?.id || null;
52
+ }
53
+ /**
54
+ * Resolve notebook ID to UUID (for API calls)
55
+ */
56
+ function resolveNotebookUuid(notebookId) {
57
+ if (notebookId && UUID_PATTERN.test(notebookId)) {
24
58
  return notebookId;
25
59
  }
26
- const activeNotebook = library.getActiveNotebook();
27
- if (!activeNotebook) {
28
- log.warning('⚠️ No notebook specified and no active notebook selected');
60
+ const notebook = notebookId
61
+ ? library.getNotebook(notebookId)
62
+ : library.getActiveNotebook();
63
+ if (!notebook) {
29
64
  return null;
30
65
  }
31
- return activeNotebook.id;
66
+ return extractUuidFromUrl(notebook.url);
32
67
  }
33
68
  /**
34
- * Handle list_sources tool
69
+ * Handle list_sources tool (browser-only)
35
70
  */
36
71
  async function handleListSources(args, sendProgress) {
37
- const notebookId = resolveNotebookId(args.notebook_id);
38
- if (!notebookId) {
72
+ const notebookUrl = resolveNotebookUrl(args.notebook_id);
73
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
74
+ if (!notebookUrl) {
39
75
  return {
40
76
  success: false,
41
77
  error: 'No notebook specified and no active notebook selected',
42
78
  };
43
79
  }
44
- if (sendProgress) {
45
- await sendProgress('📚 Listing sources...');
80
+ if (!sessionManager) {
81
+ return {
82
+ success: false,
83
+ error: 'Browser session manager not available',
84
+ notebookId: notebookAlias,
85
+ };
46
86
  }
47
- // Try API first
48
- const result = await sourceOps.listSources(notebookId);
49
- // Fallback to browser if API fails and sessionManager available
50
- if (!result.success && sessionManager) {
51
- log.warning('⚠️ API failed, falling back to browser automation...');
52
- if (sendProgress) {
53
- await sendProgress('🔄 API failed, trying browser automation...');
54
- }
55
- try {
56
- const notebookUrl = buildNotebookUrl(notebookId);
57
- const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
58
- const sources = await session.listSourcesViaUI();
59
- log.success(`✅ Found ${sources.length} sources via browser automation`);
60
- if (sendProgress) {
61
- await sendProgress(`✅ Found ${sources.length} sources via browser`);
62
- }
63
- return {
64
- success: true,
65
- sources: sources.map(s => ({
66
- title: s.title,
67
- id: s.id,
68
- })),
69
- method: 'browser',
70
- };
71
- }
72
- catch (browserError) {
73
- log.error(`❌ Browser fallback failed: ${browserError}`);
74
- }
87
+ if (sendProgress)
88
+ await sendProgress('Listing sources via browser...');
89
+ try {
90
+ const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
91
+ const sources = await session.listSourcesViaUI();
92
+ log.success(`Found ${sources.length} sources via browser`);
93
+ if (sendProgress)
94
+ await sendProgress(`Found ${sources.length} sources`);
95
+ return {
96
+ success: true,
97
+ sources: sources.map(s => ({ title: s.title, id: s.id })),
98
+ method: 'browser',
99
+ notebookId: notebookAlias,
100
+ };
75
101
  }
76
- if (result.success && sendProgress) {
77
- await sendProgress(`✅ Found ${result.sources?.length || 0} sources`);
102
+ catch (error) {
103
+ log.error(`Browser failed: ${error}`);
104
+ return { success: false, error: `Browser failed: ${error}`, notebookId: notebookAlias };
78
105
  }
79
- return result;
80
106
  }
81
107
  /**
82
- * Handle add_url_source tool
108
+ * Handle add_url_source tool (browser-only)
83
109
  */
84
110
  async function handleAddURLSource(args, sendProgress) {
85
- const notebookId = resolveNotebookId(args.notebook_id);
86
- if (!notebookId) {
111
+ const notebookUrl = resolveNotebookUrl(args.notebook_id);
112
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
113
+ if (!notebookUrl || !sessionManager) {
87
114
  return {
88
115
  success: false,
89
- error: 'No notebook specified and no active notebook selected',
116
+ error: !notebookUrl ? 'No notebook specified' : 'Browser session not available',
117
+ notebookId: notebookAlias,
90
118
  };
91
119
  }
92
- if (sendProgress) {
93
- await sendProgress(`🔗 Adding URL source: ${args.url}`);
94
- }
95
- // Try API first
96
- const result = await sourceOps.addURLSource(notebookId, args.url);
97
- // Fallback to browser if API fails and sessionManager available
98
- if (!result.success && sessionManager) {
99
- log.warning('⚠️ API failed, falling back to browser automation...');
100
- if (sendProgress) {
101
- await sendProgress('🔄 API failed, trying browser automation...');
102
- }
103
- try {
104
- const notebookUrl = buildNotebookUrl(notebookId);
105
- const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
106
- const browserSuccess = await session.addURLSourceViaUI(args.url);
107
- if (browserSuccess) {
108
- log.success('✅ URL source added via browser automation');
109
- if (sendProgress) {
110
- await sendProgress('✅ URL source added via browser');
111
- }
112
- return { success: true, notebookId, method: 'browser' };
113
- }
114
- }
115
- catch (browserError) {
116
- log.error(`❌ Browser fallback failed: ${browserError}`);
120
+ if (sendProgress)
121
+ await sendProgress(`Adding URL source: ${args.url}`);
122
+ try {
123
+ const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
124
+ const success = await session.addURLSourceViaUI(args.url);
125
+ if (success) {
126
+ log.success('URL source added via browser');
127
+ if (sendProgress)
128
+ await sendProgress('URL source added');
129
+ return { success: true, notebookId: notebookAlias, method: 'browser' };
117
130
  }
131
+ return { success: false, error: 'Browser failed to add URL source', notebookId: notebookAlias };
118
132
  }
119
- if (result.success && sendProgress) {
120
- await sendProgress('✅ URL source added successfully');
133
+ catch (error) {
134
+ log.error(`Browser failed: ${error}`);
135
+ return { success: false, error: `Browser failed: ${error}`, notebookId: notebookAlias };
121
136
  }
122
- return result;
123
137
  }
124
138
  /**
125
- * Handle add_text_source tool
139
+ * Handle add_text_source tool (browser-only)
126
140
  */
127
141
  async function handleAddTextSource(args, sendProgress) {
128
- const notebookId = resolveNotebookId(args.notebook_id);
129
- if (!notebookId) {
142
+ const notebookUrl = resolveNotebookUrl(args.notebook_id);
143
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
144
+ if (!notebookUrl || !sessionManager) {
130
145
  return {
131
146
  success: false,
132
- error: 'No notebook specified and no active notebook selected',
147
+ error: !notebookUrl ? 'No notebook specified' : 'Browser session not available',
148
+ notebookId: notebookAlias,
133
149
  };
134
150
  }
135
- if (sendProgress) {
136
- await sendProgress(`📝 Adding text source: ${args.title}`);
137
- }
138
- // Try API first
139
- const result = await sourceOps.addTextSource(notebookId, args.title, args.content);
140
- // Fallback to browser if API fails and sessionManager available
141
- if (!result.success && sessionManager) {
142
- log.warning('⚠️ API failed, falling back to browser automation...');
143
- if (sendProgress) {
144
- await sendProgress('🔄 API failed, trying browser automation...');
145
- }
146
- try {
147
- const notebookUrl = buildNotebookUrl(notebookId);
148
- const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
149
- const browserSuccess = await session.addTextSourceViaUI(args.title, args.content);
150
- if (browserSuccess) {
151
- log.success('✅ Text source added via browser automation');
152
- if (sendProgress) {
153
- await sendProgress('✅ Text source added via browser');
154
- }
155
- return { success: true, notebookId, method: 'browser' };
156
- }
157
- }
158
- catch (browserError) {
159
- log.error(`❌ Browser fallback failed: ${browserError}`);
151
+ if (sendProgress)
152
+ await sendProgress(`Adding text source: ${args.title}`);
153
+ try {
154
+ const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
155
+ const success = await session.addTextSourceViaUI(args.title, args.content);
156
+ if (success) {
157
+ log.success('Text source added via browser');
158
+ if (sendProgress)
159
+ await sendProgress('Text source added');
160
+ return { success: true, notebookId: notebookAlias, method: 'browser' };
160
161
  }
162
+ return { success: false, error: 'Browser failed to add text source', notebookId: notebookAlias };
161
163
  }
162
- if (result.success && sendProgress) {
163
- await sendProgress('✅ Text source added successfully');
164
+ catch (error) {
165
+ log.error(`Browser failed: ${error}`);
166
+ return { success: false, error: `Browser failed: ${error}`, notebookId: notebookAlias };
164
167
  }
165
- return result;
166
168
  }
167
169
  /**
168
- * Handle add_youtube_source tool
170
+ * Handle add_youtube_source tool (browser-only)
169
171
  */
170
172
  async function handleAddYouTubeSource(args, sendProgress) {
171
- const notebookId = resolveNotebookId(args.notebook_id);
172
- if (!notebookId) {
173
+ const notebookUrl = resolveNotebookUrl(args.notebook_id);
174
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
175
+ if (!notebookUrl || !sessionManager) {
173
176
  return {
174
177
  success: false,
175
- error: 'No notebook specified and no active notebook selected',
178
+ error: !notebookUrl ? 'No notebook specified' : 'Browser session not available',
179
+ notebookId: notebookAlias,
176
180
  };
177
181
  }
178
- if (sendProgress) {
179
- await sendProgress(`🎥 Adding YouTube source: ${args.youtube_url}`);
180
- }
181
- // Try API first
182
- const result = await sourceOps.addYouTubeSource(notebookId, args.youtube_url);
183
- // Fallback to browser if API fails and sessionManager available
184
- if (!result.success && sessionManager) {
185
- log.warning('⚠️ API failed, falling back to browser automation...');
186
- if (sendProgress) {
187
- await sendProgress('🔄 API failed, trying browser automation...');
188
- }
189
- try {
190
- const notebookUrl = buildNotebookUrl(notebookId);
191
- const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
192
- const browserSuccess = await session.addYouTubeSourceViaUI(args.youtube_url);
193
- if (browserSuccess) {
194
- log.success('✅ YouTube source added via browser automation');
195
- if (sendProgress) {
196
- await sendProgress('✅ YouTube source added via browser');
197
- }
198
- return { success: true, notebookId, method: 'browser' };
199
- }
200
- }
201
- catch (browserError) {
202
- log.error(`❌ Browser fallback failed: ${browserError}`);
182
+ if (sendProgress)
183
+ await sendProgress(`Adding YouTube source: ${args.youtube_url}`);
184
+ try {
185
+ const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
186
+ const success = await session.addYouTubeSourceViaUI(args.youtube_url);
187
+ if (success) {
188
+ log.success('YouTube source added via browser');
189
+ if (sendProgress)
190
+ await sendProgress('YouTube source added');
191
+ return { success: true, notebookId: notebookAlias, method: 'browser' };
203
192
  }
193
+ return { success: false, error: 'Browser failed to add YouTube source', notebookId: notebookAlias };
204
194
  }
205
- if (result.success && sendProgress) {
206
- await sendProgress('✅ YouTube source added successfully');
195
+ catch (error) {
196
+ log.error(`Browser failed: ${error}`);
197
+ return { success: false, error: `Browser failed: ${error}`, notebookId: notebookAlias };
207
198
  }
208
- return result;
209
199
  }
210
200
  /**
211
- * Handle add_drive_source tool
201
+ * Handle add_drive_source tool (API-only - requires Drive picker)
212
202
  */
213
203
  async function handleAddDriveSource(args, sendProgress) {
214
- const notebookId = resolveNotebookId(args.notebook_id);
215
- if (!notebookId) {
204
+ const notebookUuid = resolveNotebookUuid(args.notebook_id);
205
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
206
+ if (!notebookUuid) {
216
207
  return {
217
208
  success: false,
218
209
  error: 'No notebook specified and no active notebook selected',
210
+ notebookId: notebookAlias,
219
211
  };
220
212
  }
221
- if (sendProgress) {
222
- await sendProgress(`📁 Adding Google Drive source: ${args.file_id}`);
223
- }
224
- const result = await sourceOps.addDriveSource(notebookId, args.file_id);
225
- if (result.success && sendProgress) {
226
- await sendProgress('✅ Google Drive source added successfully');
227
- }
228
- return result;
213
+ if (sendProgress)
214
+ await sendProgress(`Adding Google Drive source: ${args.file_id}`);
215
+ const result = await sourceOps.addDriveSource(notebookUuid, args.file_id);
216
+ if (result.success && sendProgress)
217
+ await sendProgress('Google Drive source added');
218
+ return { ...result, notebookId: notebookAlias };
229
219
  }
230
220
  /**
231
- * Handle delete_source tool
221
+ * Handle delete_source tool (browser-only)
232
222
  */
233
223
  async function handleDeleteSource(args, sendProgress) {
234
- const notebookId = resolveNotebookId(args.notebook_id);
235
- if (!notebookId) {
224
+ const notebookUrl = resolveNotebookUrl(args.notebook_id);
225
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
226
+ if (!notebookUrl || !sessionManager) {
236
227
  return {
237
228
  success: false,
238
- error: 'No notebook specified and no active notebook selected',
229
+ error: !notebookUrl ? 'No notebook specified' : 'Browser session not available',
230
+ notebookId: notebookAlias ?? undefined,
239
231
  };
240
232
  }
241
- if (sendProgress) {
242
- await sendProgress(`🗑️ Deleting source: ${args.source_id}`);
233
+ if (sendProgress)
234
+ await sendProgress(`Deleting source: ${args.source_id}`);
235
+ try {
236
+ const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
237
+ // source_id can be title or ID - use as title for browser
238
+ let sourceTitle = args.source_id;
239
+ // If it looks like an API source ID (sources/xxx), find the actual title
240
+ if (args.source_id.startsWith('sources/')) {
241
+ const sources = await session.listSourcesViaUI();
242
+ const match = sources.find(s => s.id?.includes(args.source_id.replace('sources/', '')));
243
+ if (match)
244
+ sourceTitle = match.title;
245
+ }
246
+ const success = await session.deleteSourceViaUI(sourceTitle, sendProgress);
247
+ if (success) {
248
+ log.success('Source deleted via browser');
249
+ return { success: true, notebookId: notebookAlias ?? undefined, method: 'browser' };
250
+ }
251
+ return { success: false, error: 'Failed to delete source', notebookId: notebookAlias ?? undefined };
243
252
  }
244
- const result = await sourceOps.deleteSource(notebookId, args.source_id);
245
- if (result.success && sendProgress) {
246
- await sendProgress('✅ Source deleted successfully');
253
+ catch (error) {
254
+ log.error(`Browser failed: ${error}`);
255
+ return { success: false, error: `Browser failed: ${error}`, notebookId: notebookAlias ?? undefined };
247
256
  }
248
- return result;
249
257
  }
250
258
  /**
251
- * Handle summarize_source tool
259
+ * Handle summarize_source tool (browser-only)
252
260
  */
253
261
  async function handleSummarizeSource(args, sendProgress) {
254
- const notebookId = resolveNotebookId(args.notebook_id);
255
- if (!notebookId) {
262
+ const notebookUrl = resolveNotebookUrl(args.notebook_id);
263
+ const notebookAlias = resolveNotebookAlias(args.notebook_id);
264
+ if (!notebookUrl || !sessionManager) {
256
265
  return {
257
266
  success: false,
258
- error: 'No notebook specified and no active notebook selected',
267
+ error: !notebookUrl ? 'No notebook specified' : 'Browser session not available',
268
+ notebookId: notebookAlias ?? undefined,
259
269
  };
260
270
  }
261
- if (sendProgress) {
262
- await sendProgress(`📄 Getting source summary: ${args.source_id}`);
271
+ if (sendProgress)
272
+ await sendProgress(`Getting source details: ${args.source_id}`);
273
+ try {
274
+ const session = await sessionManager.getOrCreateSession(undefined, notebookUrl);
275
+ // source_id can be title or ID - use as title for browser
276
+ let sourceTitle = args.source_id;
277
+ // If it looks like an API source ID (sources/xxx), find the actual title
278
+ if (args.source_id.startsWith('sources/')) {
279
+ const sources = await session.listSourcesViaUI();
280
+ const match = sources.find(s => s.id?.includes(args.source_id.replace('sources/', '')));
281
+ if (match)
282
+ sourceTitle = match.title;
283
+ }
284
+ const details = await session.getSourceDetailsViaUI(sourceTitle);
285
+ if (details) {
286
+ log.success('Source details retrieved via browser');
287
+ if (sendProgress)
288
+ await sendProgress('Source details retrieved');
289
+ return {
290
+ success: true,
291
+ source: details,
292
+ notebookId: notebookAlias ?? undefined,
293
+ method: 'browser',
294
+ };
295
+ }
296
+ return { success: false, error: 'Source not found', notebookId: notebookAlias ?? undefined };
263
297
  }
264
- const result = await sourceOps.summarizeSource(notebookId, args.source_id);
265
- if (result.success && sendProgress) {
266
- await sendProgress('✅ Source summary retrieved');
298
+ catch (error) {
299
+ log.error(`Browser failed: ${error}`);
300
+ return { success: false, error: `Browser failed: ${error}`, notebookId: notebookAlias ?? undefined };
267
301
  }
268
- return result;
269
302
  }
270
303
  return {
271
304
  handleListSources,