@hasna/connectors 1.3.22 → 1.3.23

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 (63) hide show
  1. package/bin/index.js +109 -59
  2. package/bin/mcp.js +236 -182
  3. package/bin/serve.js +77 -33
  4. package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
  5. package/connectors/connect-dropbox/src/api/index.ts +4 -0
  6. package/connectors/connect-dropbox/src/cli/index.ts +151 -0
  7. package/connectors/connect-github/src/api/bulk.ts +328 -0
  8. package/connectors/connect-github/src/api/index.ts +4 -0
  9. package/connectors/connect-github/src/cli/index.ts +145 -0
  10. package/connectors/connect-gmail/src/api/history.ts +82 -0
  11. package/connectors/connect-gmail/src/api/index.ts +12 -0
  12. package/connectors/connect-gmail/src/api/settings.ts +280 -0
  13. package/connectors/connect-gmail/src/api/watch.ts +40 -0
  14. package/connectors/connect-gmail/src/cli/index.ts +237 -0
  15. package/connectors/connect-googleads/src/api/bulk.ts +354 -0
  16. package/connectors/connect-googleads/src/api/index.ts +4 -0
  17. package/connectors/connect-googleads/src/cli/index.ts +192 -0
  18. package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
  19. package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
  20. package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
  21. package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
  22. package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
  23. package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
  24. package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
  25. package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
  26. package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
  27. package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
  28. package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
  29. package/connectors/connect-googledocs/src/api/index.ts +4 -0
  30. package/connectors/connect-googledocs/src/cli/index.ts +187 -0
  31. package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
  32. package/connectors/connect-googledrive/src/api/changes.ts +139 -0
  33. package/connectors/connect-googledrive/src/api/client.ts +34 -0
  34. package/connectors/connect-googledrive/src/api/index.ts +12 -0
  35. package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
  36. package/connectors/connect-googledrive/src/cli/index.ts +624 -0
  37. package/connectors/connect-googledrive/src/index.ts +2 -0
  38. package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
  39. package/connectors/connect-googlephotos/src/api/index.ts +8 -0
  40. package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
  41. package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
  42. package/connectors/connect-googletasks/src/api/index.ts +1 -0
  43. package/connectors/connect-googletasks/src/cli/index.ts +178 -0
  44. package/connectors/connect-linear/src/api/bulk.ts +219 -0
  45. package/connectors/connect-linear/src/api/index.ts +4 -0
  46. package/connectors/connect-linear/src/cli/index.ts +116 -0
  47. package/connectors/connect-shopify/src/api/bulk.ts +288 -0
  48. package/connectors/connect-shopify/src/api/index.ts +4 -0
  49. package/connectors/connect-shopify/src/cli/index.ts +108 -0
  50. package/connectors/connect-slack/src/api/bulk.ts +237 -0
  51. package/connectors/connect-slack/src/api/index.ts +4 -0
  52. package/connectors/connect-slack/src/cli/index.ts +157 -0
  53. package/connectors/connect-stripe/src/api/bulk.ts +488 -0
  54. package/connectors/connect-stripe/src/api/index.ts +4 -0
  55. package/connectors/connect-stripe/src/cli/index.ts +206 -0
  56. package/dist/index.js +75 -33
  57. package/dist/lib/runner.d.ts +15 -5
  58. package/package.json +2 -2
  59. package/connectors/connect-cloudflare/bun.lock +0 -32
  60. package/connectors/connect-gmail/bin/index.js +0 -7027
  61. package/connectors/connect-gmail/dist/cli/index.js +0 -7035
  62. package/connectors/connect-gmail/dist/index.js +0 -2174
  63. package/dist/server/server.test.d.ts +0 -1
@@ -0,0 +1,452 @@
1
+ import type { GoogleCalendarClient } from './client.ts';
2
+ import type { Event, EventListResponse, ListEventsParams, EventInput } from '../types/index.ts';
3
+
4
+ // ============================================
5
+ // Bulk Operation Types
6
+ // ============================================
7
+
8
+ export interface BulkOperationOptions {
9
+ /** Calendar ID (default: 'primary') */
10
+ calendarId?: string;
11
+ /** Search query for events */
12
+ query?: string;
13
+ /** Filter by date range start (RFC3339) */
14
+ timeMin?: string;
15
+ /** Filter by date range end (RFC3339) */
16
+ timeMax?: string;
17
+ /** Maximum events to process (default: 100) */
18
+ maxResults?: number;
19
+ /** Maximum concurrent API calls (default: 10) */
20
+ concurrency?: number;
21
+ /** Dry run - don't actually modify, just preview */
22
+ dryRun?: boolean;
23
+ /** Progress callback */
24
+ onProgress?: (current: number, total: number, event: EventSummary) => void;
25
+ /** Error callback */
26
+ onError?: (error: Error, event: EventSummary) => void;
27
+ }
28
+
29
+ export interface BulkCreateOptions {
30
+ calendarId?: string;
31
+ /** Events to create in batch */
32
+ events: EventInput[];
33
+ /** Maximum concurrent API calls (default: 10) */
34
+ concurrency?: number;
35
+ /** Dry run - don't actually create, just preview */
36
+ dryRun?: boolean;
37
+ /** Send notifications to attendees */
38
+ sendNotifications?: boolean;
39
+ /** Progress callback */
40
+ onProgress?: (current: number, total: number, event: EventInput) => void;
41
+ /** Error callback */
42
+ onError?: (error: Error, event: EventInput) => void;
43
+ }
44
+
45
+ export interface BulkUpdateOptions extends BulkOperationOptions {
46
+ /** Partial event data to apply to all matching events */
47
+ updates: Partial<EventInput>;
48
+ }
49
+
50
+ export interface BulkRespondOptions extends BulkOperationOptions {
51
+ /** Response status to apply */
52
+ response: 'accepted' | 'declined' | 'tentative';
53
+ /** Optional comment to add */
54
+ comment?: string;
55
+ }
56
+
57
+ export interface EventSummary {
58
+ id: string;
59
+ summary: string;
60
+ status: string;
61
+ start: string;
62
+ end: string;
63
+ location?: string;
64
+ htmlLink?: string;
65
+ }
66
+
67
+ export interface BulkOperationResult {
68
+ total: number;
69
+ success: number;
70
+ failed: number;
71
+ skipped: number;
72
+ errors: Array<{ eventId: string; error: string }>;
73
+ processedEvents: EventSummary[];
74
+ }
75
+
76
+ export interface PreviewResult {
77
+ events: EventSummary[];
78
+ total: number;
79
+ query?: string;
80
+ calendarId: string;
81
+ }
82
+
83
+ // ============================================
84
+ // Bulk Operations API
85
+ // ============================================
86
+
87
+ export class BulkApi {
88
+ private readonly client: GoogleCalendarClient;
89
+
90
+ constructor(client: GoogleCalendarClient) {
91
+ this.client = client;
92
+ }
93
+
94
+ // ============================================
95
+ // Preview Operations
96
+ // ============================================
97
+
98
+ /**
99
+ * Preview events matching a query without making changes
100
+ */
101
+ async preview(calendarId: string = 'primary', options?: {
102
+ query?: string;
103
+ timeMin?: string;
104
+ timeMax?: string;
105
+ maxResults?: number;
106
+ }): Promise<PreviewResult> {
107
+ const events = await this.fetchEvents(calendarId, {
108
+ q: options?.query,
109
+ timeMin: options?.timeMin,
110
+ timeMax: options?.timeMax,
111
+ maxResults: options?.maxResults || 50,
112
+ orderBy: 'startTime',
113
+ singleEvents: true,
114
+ });
115
+ return { events, total: events.length, query: options?.query, calendarId };
116
+ }
117
+
118
+ // ============================================
119
+ // Delete Operations
120
+ // ============================================
121
+
122
+ /**
123
+ * Bulk delete events matching a query
124
+ */
125
+ async delete(options: BulkOperationOptions): Promise<BulkOperationResult> {
126
+ const calendarId = options.calendarId || 'primary';
127
+ const events = await this.fetchEvents(calendarId, {
128
+ q: options.query,
129
+ timeMin: options.timeMin,
130
+ timeMax: options.timeMax,
131
+ maxResults: options.maxResults || 100,
132
+ });
133
+
134
+ return this.executeBatch(events, {
135
+ dryRun: options.dryRun || false,
136
+ concurrency: options.concurrency || 10,
137
+ onProgress: options.onProgress,
138
+ onError: options.onError,
139
+ operation: async (event) => {
140
+ await this.client.delete(
141
+ '/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id)
142
+ );
143
+ },
144
+ });
145
+ }
146
+
147
+ // ============================================
148
+ // Update Operations
149
+ // ============================================
150
+
151
+ /**
152
+ * Bulk update events matching a query with partial data
153
+ */
154
+ async update(options: BulkUpdateOptions): Promise<BulkOperationResult> {
155
+ const calendarId = options.calendarId || 'primary';
156
+ const events = await this.fetchEvents(calendarId, {
157
+ q: options.query,
158
+ timeMin: options.timeMin,
159
+ timeMax: options.timeMax,
160
+ maxResults: options.maxResults || 100,
161
+ });
162
+
163
+ return this.executeBatch(events, {
164
+ dryRun: options.dryRun || false,
165
+ concurrency: options.concurrency || 10,
166
+ onProgress: options.onProgress,
167
+ onError: options.onError,
168
+ operation: async (event) => {
169
+ await this.client.patch<Event>(
170
+ '/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id),
171
+ options.updates as Record<string, unknown>
172
+ );
173
+ },
174
+ });
175
+ }
176
+
177
+ // ============================================
178
+ // Create Operations
179
+ // ============================================
180
+
181
+ /**
182
+ * Bulk create events from a list
183
+ */
184
+ async create(options: BulkCreateOptions): Promise<BulkOperationResult> {
185
+ const calendarId = options.calendarId || 'primary';
186
+ const result: BulkOperationResult = {
187
+ total: options.events.length,
188
+ success: 0,
189
+ failed: 0,
190
+ skipped: 0,
191
+ errors: [],
192
+ processedEvents: [],
193
+ };
194
+
195
+ if (options.events.length === 0) {
196
+ return result;
197
+ }
198
+
199
+ const chunks = this.chunkArray(options.events, options.concurrency || 10);
200
+
201
+ for (const chunk of chunks) {
202
+ await Promise.all(
203
+ chunk.map(async (eventInput) => {
204
+ try {
205
+ if (options.dryRun) {
206
+ result.success++;
207
+ result.processedEvents.push({
208
+ id: '(dry-run)',
209
+ summary: eventInput.summary || '(untitled)',
210
+ status: 'confirmed',
211
+ start: eventInput.start.dateTime || eventInput.start.date || '',
212
+ end: eventInput.end.dateTime || eventInput.end.date || '',
213
+ location: eventInput.location,
214
+ });
215
+ } else {
216
+ const created = await this.client.post<Event>(
217
+ '/calendars/' + encodeURIComponent(calendarId) + '/events',
218
+ eventInput as Record<string, unknown>,
219
+ { sendNotifications: options.sendNotifications }
220
+ );
221
+ result.success++;
222
+ result.processedEvents.push({
223
+ id: created.id,
224
+ summary: created.summary || '(untitled)',
225
+ status: created.status || 'confirmed',
226
+ start: created.start?.dateTime || created.start?.date || '',
227
+ end: created.end?.dateTime || created.end?.date || '',
228
+ location: created.location,
229
+ htmlLink: created.htmlLink,
230
+ });
231
+ }
232
+
233
+ if (options.onProgress) {
234
+ options.onProgress(result.success + result.failed, result.total, eventInput);
235
+ }
236
+ } catch (err) {
237
+ result.failed++;
238
+ const errorMessage = err instanceof Error ? err.message : String(err);
239
+ result.errors.push({ eventId: eventInput.summary || 'unknown', error: errorMessage });
240
+
241
+ if (options.onError) {
242
+ onError(err instanceof Error ? err : new Error(errorMessage), eventInput);
243
+ }
244
+ }
245
+ })
246
+ );
247
+ }
248
+
249
+ return result;
250
+ }
251
+
252
+ // ============================================
253
+ // Response Operations
254
+ // ============================================
255
+
256
+ /**
257
+ * Bulk accept event invitations
258
+ */
259
+ async accept(options: BulkRespondOptions): Promise<BulkOperationResult> {
260
+ return this.#respond({ ...options, response: 'accepted' });
261
+ }
262
+
263
+ /**
264
+ * Bulk decline event invitations
265
+ */
266
+ async decline(options: BulkRespondOptions): Promise<BulkOperationResult> {
267
+ return this.#respond({ ...options, response: 'declined' });
268
+ }
269
+
270
+ /**
271
+ * Bulk mark events as tentative
272
+ */
273
+ async tentative(options: BulkRespondOptions): Promise<BulkOperationResult> {
274
+ return this.#respond({ ...options, response: 'tentative' });
275
+ }
276
+
277
+ // ============================================
278
+ // Helper Methods
279
+ // ============================================
280
+
281
+ /**
282
+ * Bulk respond to events (accept/decline/tentative)
283
+ */
284
+ async #respond(options: BulkRespondOptions): Promise<BulkOperationResult> {
285
+ const calendarId = options.calendarId || 'primary';
286
+ const events = await this.fetchEvents(calendarId, {
287
+ q: options.query,
288
+ timeMin: options.timeMin,
289
+ timeMax: options.timeMax,
290
+ maxResults: options.maxResults || 100,
291
+ });
292
+
293
+ return this.executeBatch(events, {
294
+ dryRun: options.dryRun || false,
295
+ concurrency: options.concurrency || 10,
296
+ onProgress: options.onProgress,
297
+ onError: options.onError,
298
+ operation: async (event) => {
299
+ // Get the current event to find self attendee
300
+ const current = await this.client.get<Event>(
301
+ '/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id)
302
+ );
303
+
304
+ if (current.attendees) {
305
+ for (const attendee of current.attendees) {
306
+ if (attendee.self) {
307
+ attendee.responseStatus = options.response;
308
+ if (options.comment) {
309
+ attendee.comment = options.comment;
310
+ }
311
+ break;
312
+ }
313
+ }
314
+ }
315
+
316
+ await this.client.patch<Event>(
317
+ '/calendars/' + encodeURIComponent(calendarId) + '/events/' + encodeURIComponent(event.id),
318
+ { attendees: current.attendees } as Record<string, unknown>,
319
+ { sendUpdates: 'none' }
320
+ );
321
+ },
322
+ });
323
+ }
324
+
325
+ /**
326
+ * Fetch events matching criteria
327
+ */
328
+ async fetchEvents(calendarId: string, params: {
329
+ q?: string;
330
+ timeMin?: string;
331
+ timeMax?: string;
332
+ maxResults?: number;
333
+ orderBy?: string;
334
+ singleEvents?: boolean;
335
+ }): Promise<EventSummary[]> {
336
+ const events: EventSummary[] = [];
337
+ let pageToken: string | undefined;
338
+ const max = params.maxResults || 100;
339
+
340
+ while (events.length < max) {
341
+ const requestParams: Record<string, string | number | boolean | undefined> = {
342
+ maxResults: Math.min(100, max - events.length),
343
+ pageToken,
344
+ orderBy: params.orderBy,
345
+ singleEvents: params.singleEvents,
346
+ };
347
+
348
+ if (params.q) requestParams.q = params.q;
349
+ if (params.timeMin) requestParams.timeMin = params.timeMin;
350
+ if (params.timeMax) requestParams.timeMax = params.timeMax;
351
+
352
+ const response = await this.client.get<EventListResponse>(
353
+ '/calendars/' + encodeURIComponent(calendarId) + '/events',
354
+ requestParams
355
+ );
356
+
357
+ if (!response.items || response.items.length === 0) {
358
+ break;
359
+ }
360
+
361
+ for (const e of response.items) {
362
+ events.push({
363
+ id: e.id,
364
+ summary: e.summary || '(untitled)',
365
+ status: e.status || 'confirmed',
366
+ start: e.start?.dateTime || e.start?.date || '',
367
+ end: e.end?.dateTime || e.end?.date || '',
368
+ location: e.location,
369
+ htmlLink: e.htmlLink,
370
+ });
371
+ }
372
+
373
+ pageToken = response.nextPageToken;
374
+ if (!pageToken) break;
375
+ }
376
+
377
+ return events;
378
+ }
379
+
380
+ /**
381
+ * Execute operations in batches with concurrency control
382
+ */
383
+ private async executeBatch(
384
+ events: EventSummary[],
385
+ options: {
386
+ dryRun: boolean;
387
+ concurrency: number;
388
+ onProgress?: (current: number, total: number, event: EventSummary) => void;
389
+ onError?: (error: Error, event: EventSummary) => void;
390
+ operation: (event: EventSummary) => Promise<void>;
391
+ }
392
+ ): Promise<BulkOperationResult> {
393
+ const { dryRun, concurrency, onProgress, onError, operation } = options;
394
+
395
+ const result: BulkOperationResult = {
396
+ total: events.length,
397
+ success: 0,
398
+ failed: 0,
399
+ skipped: 0,
400
+ errors: [],
401
+ processedEvents: [],
402
+ };
403
+
404
+ if (events.length === 0) {
405
+ return result;
406
+ }
407
+
408
+ const chunks = this.chunkArray(events, concurrency);
409
+
410
+ for (const chunk of chunks) {
411
+ await Promise.all(
412
+ chunk.map(async (event) => {
413
+ try {
414
+ if (dryRun) {
415
+ result.success++;
416
+ result.processedEvents.push(event);
417
+ } else {
418
+ await operation(event);
419
+ result.success++;
420
+ result.processedEvents.push(event);
421
+ }
422
+
423
+ if (onProgress) {
424
+ onProgress(result.success + result.failed, result.total, event);
425
+ }
426
+ } catch (err) {
427
+ result.failed++;
428
+ const errorMessage = err instanceof Error ? err.message : String(err);
429
+ result.errors.push({ eventId: event.id, error: errorMessage });
430
+
431
+ if (onError) {
432
+ onError(err instanceof Error ? err : new Error(errorMessage), event);
433
+ }
434
+ }
435
+ })
436
+ );
437
+ }
438
+
439
+ return result;
440
+ }
441
+
442
+ /**
443
+ * Split array into chunks
444
+ */
445
+ private chunkArray<T>(array: T[], size: number): T[][] {
446
+ const chunks: T[][] = [];
447
+ for (let i = 0; i < array.length; i += size) {
448
+ chunks.push(array.slice(i, i + size));
449
+ }
450
+ return chunks;
451
+ }
452
+ }
@@ -0,0 +1,35 @@
1
+ import type { GoogleCalendarClient } from './client';
2
+ import type { FreeBusyRequest, FreeBusyCalendarResponse } from '../types';
3
+
4
+ /**
5
+ * Free/Busy API module - query free/busy time for calendars
6
+ */
7
+ export class FreeBusyApi {
8
+ constructor(private readonly client: GoogleCalendarClient) {}
9
+
10
+ /**
11
+ * Query free/busy information for one or more calendars
12
+ */
13
+ async query(request: FreeBusyRequest): Promise<FreeBusyCalendarResponse> {
14
+ return this.client.post<FreeBusyCalendarResponse>('/freeBusy', {
15
+ timeMin: request.timeMin || new Date().toISOString(),
16
+ timeMax: request.timeMax || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
17
+ items: request.items,
18
+ groupExpansionMax: request.groupExpansionMax,
19
+ timeZone: request.timeZone,
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Quick check if a calendar is free during a time range
25
+ */
26
+ async isFree(calendarId: string, start: string, end: string): Promise<boolean> {
27
+ const result = await this.query({
28
+ timeMin: start,
29
+ timeMax: end,
30
+ items: [{ id: calendarId }],
31
+ });
32
+ const calendar = result.calendars[calendarId];
33
+ return !calendar || calendar.busy.length === 0;
34
+ }
35
+ }
@@ -2,6 +2,9 @@ import type { GoogleCalendarConfig } from '../types';
2
2
  import { GoogleCalendarClient, CALENDAR_SCOPES } from './client';
3
3
  import { CalendarsApi } from './calendars';
4
4
  import { EventsApi } from './events';
5
+ import { BulkApi } from './bulk';
6
+ import { FreeBusyApi } from './freebusy';
7
+ import { AclApi } from './acl';
5
8
 
6
9
  /**
7
10
  * Main GoogleCalendar class
@@ -12,11 +15,17 @@ export class GoogleCalendar {
12
15
  // API modules
13
16
  public readonly calendars: CalendarsApi;
14
17
  public readonly events: EventsApi;
18
+ public readonly bulk: BulkApi;
19
+ public readonly freebusy: FreeBusyApi;
20
+ public readonly acl: AclApi;
15
21
 
16
22
  constructor(config: GoogleCalendarConfig) {
17
23
  this.client = new GoogleCalendarClient(config);
18
24
  this.calendars = new CalendarsApi(this.client);
19
25
  this.events = new EventsApi(this.client);
26
+ this.bulk = new BulkApi(this.client);
27
+ this.freebusy = new FreeBusyApi(this.client);
28
+ this.acl = new AclApi(this.client);
20
29
  }
21
30
 
22
31
  /**