@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,365 @@
1
+ import type { GoogleContactsClient } from './client';
2
+ import type { Contact, ContactsListResponse } from '../types';
3
+
4
+ // ============================================
5
+ // Bulk Operation Types
6
+ // ============================================
7
+
8
+ export interface BulkOperationOptions {
9
+ /** Search query to match contacts (matches name, email, etc.) */
10
+ query?: string;
11
+ /** Maximum contacts to process (default: 100) */
12
+ maxResults?: number;
13
+ /** Maximum concurrent API calls (default: 10) */
14
+ concurrency?: number;
15
+ /** Dry run - don't actually modify */
16
+ dryRun?: boolean;
17
+ /** Progress callback */
18
+ onProgress?: (current: number, total: number, contact: ContactSummary) => void;
19
+ /** Error callback */
20
+ onError?: (error: Error, contact: ContactSummary) => void;
21
+ }
22
+
23
+ export interface BulkDeleteOptions extends BulkOperationOptions {}
24
+
25
+ export interface BulkUpdateOptions extends BulkOperationOptions {
26
+ /** Fields to update on all matching contacts */
27
+ updates: {
28
+ givenName?: string;
29
+ familyName?: string;
30
+ organization?: { name?: string; title?: string };
31
+ };
32
+ }
33
+
34
+ export interface BulkGroupOptions {
35
+ /** Contact resource names to add */
36
+ contactNames?: string[];
37
+ /** Or use a query to select contacts */
38
+ query?: string;
39
+ maxResults?: number;
40
+ concurrency?: number;
41
+ dryRun?: boolean;
42
+ onProgress?: (current: number, total: number, contact: ContactSummary) => void;
43
+ onError?: (error: Error, contact: ContactSummary) => void;
44
+ }
45
+
46
+ export interface ContactSummary {
47
+ resourceName: string;
48
+ displayName: string;
49
+ emails: string[];
50
+ }
51
+
52
+ export interface BulkOperationResult {
53
+ total: number;
54
+ success: number;
55
+ failed: number;
56
+ skipped: number;
57
+ errors: Array<{ contactName: string; error: string }>;
58
+ processedContacts: ContactSummary[];
59
+ }
60
+
61
+ export interface PreviewResult {
62
+ contacts: ContactSummary[];
63
+ total: number;
64
+ query?: string;
65
+ }
66
+
67
+ // ============================================
68
+ // Bulk Operations API
69
+ // ============================================
70
+
71
+ export class BulkApi {
72
+ private readonly client: GoogleContactsClient;
73
+
74
+ constructor(client: GoogleContactsClient) {
75
+ this.client = client;
76
+ }
77
+
78
+ // ============================================
79
+ // Preview
80
+ // ============================================
81
+
82
+ /**
83
+ * Preview contacts matching a query
84
+ */
85
+ async preview(options?: { query?: string; maxResults?: number }): Promise<PreviewResult> {
86
+ const contacts = await this.fetchContacts({
87
+ query: options?.query,
88
+ maxResults: options?.maxResults || 50,
89
+ });
90
+ return { contacts, total: contacts.length, query: options?.query };
91
+ }
92
+
93
+ // ============================================
94
+ // Delete
95
+ // ============================================
96
+
97
+ /**
98
+ * Bulk delete contacts matching a query
99
+ */
100
+ async delete(options: BulkDeleteOptions): Promise<BulkOperationResult> {
101
+ const contacts = await this.fetchContacts({
102
+ query: options.query,
103
+ maxResults: options.maxResults || 100,
104
+ });
105
+
106
+ return this.executeBatch(contacts, {
107
+ dryRun: options.dryRun || false,
108
+ concurrency: options.concurrency || 10,
109
+ onProgress: options.onProgress,
110
+ onError: options.onError,
111
+ operation: async (contact) => {
112
+ await this.client.delete(`/v1/${contact.resourceName}:deleteContact`);
113
+ },
114
+ });
115
+ }
116
+
117
+ // ============================================
118
+ // Update
119
+ // ============================================
120
+
121
+ /**
122
+ * Bulk update contacts with partial data
123
+ */
124
+ async update(options: BulkUpdateOptions): Promise<BulkOperationResult> {
125
+ const contacts = await this.fetchContacts({
126
+ query: options.query,
127
+ maxResults: options.maxResults || 100,
128
+ });
129
+
130
+ return this.executeBatch(contacts, {
131
+ dryRun: options.dryRun || false,
132
+ concurrency: options.concurrency || 10,
133
+ onProgress: options.onProgress,
134
+ onError: options.onError,
135
+ operation: async (contact) => {
136
+ const updateFields: string[] = [];
137
+ const body: Record<string, unknown> = {};
138
+
139
+ if (options.updates.givenName || options.updates.familyName) {
140
+ body.names = [{
141
+ givenName: options.updates.givenName,
142
+ familyName: options.updates.familyName,
143
+ }];
144
+ updateFields.push('names');
145
+ }
146
+ if (options.updates.organization) {
147
+ body.organizations = [{
148
+ name: options.updates.organization.name,
149
+ title: options.updates.organization.title,
150
+ }];
151
+ updateFields.push('organizations');
152
+ }
153
+
154
+ await this.client.patch<Contact>(`/v1/${contact.resourceName}:updateContact`, body, {
155
+ updatePersonFields: updateFields.join(','),
156
+ });
157
+ },
158
+ });
159
+ }
160
+
161
+ // ============================================
162
+ // Add to Group
163
+ // ============================================
164
+
165
+ /**
166
+ * Bulk add contacts to a contact group
167
+ */
168
+ async addToGroup(groupResourceName: string, options: BulkGroupOptions): Promise<BulkOperationResult> {
169
+ let contactNames = options.contactNames;
170
+
171
+ // If no names provided, resolve from query
172
+ if (!contactNames || contactNames.length === 0) {
173
+ const contacts = await this.fetchContacts({
174
+ query: options.query,
175
+ maxResults: options.maxResults || 100,
176
+ });
177
+ contactNames = contacts.map(c => c.resourceName);
178
+ }
179
+
180
+ const summaries = contactNames.map(name => ({
181
+ resourceName: name,
182
+ displayName: name,
183
+ emails: [],
184
+ }));
185
+
186
+ return this.executeBatch(summaries, {
187
+ dryRun: options.dryRun || false,
188
+ concurrency: options.concurrency || 10,
189
+ onProgress: options.onProgress,
190
+ onError: options.onError,
191
+ operation: async (contact) => {
192
+ await this.client.post(`/v1/${groupResourceName}:modifyMembers`, {
193
+ membersToAdd: [{ contactGroupMembership: { contactGroupResourceName: groupResourceName } }],
194
+ resourceNames: [contact.resourceName],
195
+ });
196
+ },
197
+ });
198
+ }
199
+
200
+ // ============================================
201
+ // Remove from Group
202
+ // ============================================
203
+
204
+ /**
205
+ * Bulk remove contacts from a contact group
206
+ */
207
+ async removeFromGroup(groupResourceName: string, options: BulkGroupOptions): Promise<BulkOperationResult> {
208
+ let contactNames = options.contactNames;
209
+
210
+ if (!contactNames || contactNames.length === 0) {
211
+ const contacts = await this.fetchContacts({
212
+ query: options.query,
213
+ maxResults: options.maxResults || 100,
214
+ });
215
+ contactNames = contacts.map(c => c.resourceName);
216
+ }
217
+
218
+ const summaries = contactNames.map(name => ({
219
+ resourceName: name,
220
+ displayName: name,
221
+ emails: [],
222
+ }));
223
+
224
+ return this.executeBatch(summaries, {
225
+ dryRun: options.dryRun || false,
226
+ concurrency: options.concurrency || 10,
227
+ onProgress: options.onProgress,
228
+ onError: options.onError,
229
+ operation: async (contact) => {
230
+ await this.client.post(`/v1/${groupResourceName}:modifyMembers`, {
231
+ membersToDelete: [{ contactId: contact.resourceName.split('/').pop() }],
232
+ });
233
+ },
234
+ });
235
+ }
236
+
237
+ // ============================================
238
+ // Helper Methods
239
+ // ============================================
240
+
241
+ /**
242
+ * Fetch contacts matching a query
243
+ */
244
+ async fetchContacts(params: { query?: string; maxResults?: number }): Promise<ContactSummary[]> {
245
+ const contacts: ContactSummary[] = [];
246
+ let pageToken: string | undefined;
247
+ const max = params.maxResults || 100;
248
+ const personFields = 'names,emailAddresses,organizations,memberships';
249
+
250
+ while (contacts.length < max) {
251
+ let response: ContactsListResponse;
252
+
253
+ if (params.query) {
254
+ // Use search endpoint
255
+ const searchResult = await this.client.get<{
256
+ results?: Array<{ person: Contact }>;
257
+ nextPageToken?: string;
258
+ }>('/v1/people:searchContacts', {
259
+ query: params.query,
260
+ pageSize: Math.min(100, max - contacts.length),
261
+ pageToken,
262
+ readMask: personFields,
263
+ });
264
+
265
+ if (!searchResult.results || searchResult.results.length === 0) break;
266
+
267
+ for (const r of searchResult.results) {
268
+ const c = r.person;
269
+ contacts.push(this.toSummary(c));
270
+ }
271
+ pageToken = searchResult.nextPageToken;
272
+ } else {
273
+ // Use list endpoint
274
+ response = await this.client.get<ContactsListResponse>('/v1/people/me/connections', {
275
+ pageSize: Math.min(100, max - contacts.length),
276
+ pageToken,
277
+ personFields,
278
+ sortOrder: 'LAST_MODIFIED_DESCENDING',
279
+ });
280
+
281
+ if (!response.connections || response.connections.length === 0) break;
282
+
283
+ for (const c of response.connections) {
284
+ contacts.push(this.toSummary(c));
285
+ }
286
+ pageToken = response.nextPageToken;
287
+ }
288
+
289
+ if (!pageToken) break;
290
+ }
291
+
292
+ return contacts;
293
+ }
294
+
295
+ private toSummary(contact: Contact): ContactSummary {
296
+ return {
297
+ resourceName: contact.resourceName,
298
+ displayName: contact.names?.[0]?.displayName || '(unnamed)',
299
+ emails: (contact.emailAddresses || []).map(e => e.value).filter(Boolean),
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Execute operations in batches with concurrency control
305
+ */
306
+ private async executeBatch(
307
+ contacts: ContactSummary[],
308
+ options: {
309
+ dryRun: boolean;
310
+ concurrency: number;
311
+ onProgress?: (current: number, total: number, contact: ContactSummary) => void;
312
+ onError?: (error: Error, contact: ContactSummary) => void;
313
+ operation: (contact: ContactSummary) => Promise<void>;
314
+ }
315
+ ): Promise<BulkOperationResult> {
316
+ const { dryRun, concurrency, onProgress, onError, operation } = options;
317
+
318
+ const result: BulkOperationResult = {
319
+ total: contacts.length,
320
+ success: 0,
321
+ failed: 0,
322
+ skipped: 0,
323
+ errors: [],
324
+ processedContacts: [],
325
+ };
326
+
327
+ if (contacts.length === 0) return result;
328
+
329
+ const chunks = this.chunkArray(contacts, concurrency);
330
+
331
+ for (const chunk of chunks) {
332
+ await Promise.all(
333
+ chunk.map(async (contact) => {
334
+ try {
335
+ if (dryRun) {
336
+ result.success++;
337
+ result.processedContacts.push(contact);
338
+ } else {
339
+ await operation(contact);
340
+ result.success++;
341
+ result.processedContacts.push(contact);
342
+ }
343
+
344
+ if (onProgress) onProgress(result.success + result.failed, result.total, contact);
345
+ } catch (err) {
346
+ result.failed++;
347
+ const errorMessage = err instanceof Error ? err.message : String(err);
348
+ result.errors.push({ contactName: contact.resourceName, error: errorMessage });
349
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), contact);
350
+ }
351
+ })
352
+ );
353
+ }
354
+
355
+ return result;
356
+ }
357
+
358
+ private chunkArray<T>(array: T[], size: number): T[][] {
359
+ const chunks: T[][] = [];
360
+ for (let i = 0; i < array.length; i += size) {
361
+ chunks.push(array.slice(i, i + size));
362
+ }
363
+ return chunks;
364
+ }
365
+ }
@@ -0,0 +1,164 @@
1
+ import type { GoogleContactsClient } from './client';
2
+ import type { ContactGroup, ContactGroupsListResponse } from '../types';
3
+
4
+ // ============================================
5
+ // Group Request/Response Types
6
+ // ============================================
7
+
8
+ export interface CreateGroupParams {
9
+ name: string;
10
+ }
11
+
12
+ export interface UpdateGroupParams {
13
+ name?: string;
14
+ }
15
+
16
+ export interface GetGroupMembersOptions {
17
+ pageSize?: number;
18
+ pageToken?: string;
19
+ }
20
+
21
+ export interface GetGroupMembersResponse {
22
+ connections?: Array<{
23
+ resourceName: string;
24
+ etag?: string;
25
+ names?: Array<{
26
+ displayName?: string;
27
+ givenName?: string;
28
+ familyName?: string;
29
+ }>;
30
+ emailAddresses?: Array<{ value: string }>;
31
+ phoneNumbers?: Array<{ value: string }>;
32
+ }>;
33
+ nextPageToken?: string;
34
+ totalItems?: number;
35
+ }
36
+
37
+ export interface ModifyMembershipParams {
38
+ contactResourceName: string;
39
+ }
40
+
41
+ /**
42
+ * Groups API module - CRUD and membership management for Google contact groups
43
+ */
44
+ export class GroupsApi {
45
+ constructor(private readonly client: GoogleContactsClient) {}
46
+
47
+ /**
48
+ * List contact groups
49
+ */
50
+ async list(options: { pageSize?: number; pageToken?: string; groupFields?: string } = {}): Promise<ContactGroupsListResponse> {
51
+ const { pageSize = 100, pageToken, groupFields } = options;
52
+ const params: Record<string, string | number> = { pageSize };
53
+ if (pageToken) params.pageToken = pageToken;
54
+ if (groupFields) params.groupFields = groupFields;
55
+ return this.client.get<ContactGroupsListResponse>('/v1/contactGroups', params);
56
+ }
57
+
58
+ /**
59
+ * Get a specific group by resource name
60
+ */
61
+ async get(groupResourceName: string): Promise<ContactGroup> {
62
+ const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
63
+ return this.client.get<ContactGroup>(`/v1/${name}`);
64
+ }
65
+
66
+ /**
67
+ * Create a new contact group
68
+ */
69
+ async create(params: CreateGroupParams): Promise<ContactGroup> {
70
+ return this.client.post<ContactGroup>('/v1/contactGroups', {
71
+ contactGroup: {
72
+ name: params.name,
73
+ },
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Update a contact group's name
79
+ */
80
+ async update(groupResourceName: string, params: UpdateGroupParams): Promise<ContactGroup> {
81
+ const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
82
+
83
+ const body: Record<string, unknown> = {};
84
+ if (params.name !== undefined) {
85
+ body.name = params.name;
86
+ }
87
+
88
+ return this.client.put<ContactGroup>(`/v1/${name}`, body, {
89
+ updateMask: 'name',
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Delete a contact group
95
+ */
96
+ async delete(groupResourceName: string): Promise<void> {
97
+ const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
98
+ await this.client.delete(`/v1/${name}`);
99
+ }
100
+
101
+ /**
102
+ * List members of a contact group
103
+ */
104
+ async getMembers(groupResourceName: string, options: GetGroupMembersOptions = {}): Promise<GetGroupMembersResponse> {
105
+ const name = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
106
+ const { pageSize = 100, pageToken } = options;
107
+ const params: Record<string, string | number> = { pageSize };
108
+ if (pageToken) params.pageToken = pageToken;
109
+ return this.client.get<GetGroupMembersResponse>(`/v1/${name}/members`, params);
110
+ }
111
+
112
+ /**
113
+ * Iterate over all members of a group (handles pagination)
114
+ */
115
+ async *listAllMembers(groupResourceName: string, options: Omit<GetGroupMembersOptions, 'pageToken'> = {}): AsyncGenerator<{
116
+ resourceName: string;
117
+ displayName?: string;
118
+ emails?: string[];
119
+ phones?: string[];
120
+ }> {
121
+ let pageToken: string | undefined;
122
+ do {
123
+ const response = await this.getMembers(groupResourceName, { ...options, pageToken });
124
+ const connections = response.connections || [];
125
+ for (const member of connections) {
126
+ yield {
127
+ resourceName: member.resourceName,
128
+ displayName: member.names?.[0]?.displayName,
129
+ emails: member.emailAddresses?.map(e => e.value) || [],
130
+ phones: member.phoneNumbers?.map(p => p.value) || [],
131
+ };
132
+ }
133
+ pageToken = response.nextPageToken;
134
+ } while (pageToken);
135
+ }
136
+
137
+ /**
138
+ * Add a contact to a group
139
+ */
140
+ async addMember(groupResourceName: string, contactResourceName: string): Promise<void> {
141
+ const group = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
142
+ const contact = contactResourceName.startsWith('people/') ? contactResourceName : `people/${contactResourceName}`;
143
+ await this.client.post(`/v1/${group}/members:batchCreate`, {
144
+ contactMemberships: [
145
+ {
146
+ contact: {
147
+ resourceName: contact,
148
+ },
149
+ },
150
+ ],
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Remove a contact from a group
156
+ */
157
+ async removeMember(groupResourceName: string, contactResourceName: string): Promise<void> {
158
+ const group = groupResourceName.startsWith('contactGroups/') ? groupResourceName : `contactGroups/${groupResourceName}`;
159
+ const contact = contactResourceName.startsWith('people/') ? contactResourceName : `people/${contactResourceName}`;
160
+ await this.client.post(`/v1/${group}/members:batchDelete`, {
161
+ contactResourceNames: [contact],
162
+ });
163
+ }
164
+ }
@@ -1,6 +1,8 @@
1
1
  import type { GoogleContactsConfig, OAuthTokens } from '../types';
2
2
  import { GoogleContactsClient } from './client';
3
3
  import { ContactsApi } from './contacts';
4
+ import { GroupsApi } from './groups';
5
+ import { BulkApi } from './bulk';
4
6
 
5
7
  /**
6
8
  * Main GoogleContacts class - entry point for the API
@@ -10,10 +12,14 @@ export class GoogleContacts {
10
12
 
11
13
  // API modules
12
14
  public readonly contacts: ContactsApi;
15
+ public readonly groups: GroupsApi;
16
+ public readonly bulk: BulkApi;
13
17
 
14
18
  constructor(config: GoogleContactsConfig) {
15
19
  this.client = new GoogleContactsClient(config);
16
20
  this.contacts = new ContactsApi(this.client);
21
+ this.groups = new GroupsApi(this.client);
22
+ this.bulk = new BulkApi(this.client);
17
23
  }
18
24
 
19
25
  /**
@@ -97,3 +103,5 @@ export class GoogleContacts {
97
103
 
98
104
  export { GoogleContactsClient } from './client';
99
105
  export { ContactsApi } from './contacts';
106
+ export { GroupsApi } from './groups';
107
+ export { BulkApi } from './bulk';