@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.
- package/bin/index.js +109 -59
- package/bin/mcp.js +236 -182
- package/bin/serve.js +77 -33
- package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
- package/connectors/connect-dropbox/src/api/index.ts +4 -0
- package/connectors/connect-dropbox/src/cli/index.ts +151 -0
- package/connectors/connect-github/src/api/bulk.ts +328 -0
- package/connectors/connect-github/src/api/index.ts +4 -0
- package/connectors/connect-github/src/cli/index.ts +145 -0
- package/connectors/connect-gmail/src/api/history.ts +82 -0
- package/connectors/connect-gmail/src/api/index.ts +12 -0
- package/connectors/connect-gmail/src/api/settings.ts +280 -0
- package/connectors/connect-gmail/src/api/watch.ts +40 -0
- package/connectors/connect-gmail/src/cli/index.ts +237 -0
- package/connectors/connect-googleads/src/api/bulk.ts +354 -0
- package/connectors/connect-googleads/src/api/index.ts +4 -0
- package/connectors/connect-googleads/src/cli/index.ts +192 -0
- package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
- package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
- package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
- package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
- package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
- package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
- package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
- package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
- package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
- package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
- package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
- package/connectors/connect-googledocs/src/api/index.ts +4 -0
- package/connectors/connect-googledocs/src/cli/index.ts +187 -0
- package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
- package/connectors/connect-googledrive/src/api/changes.ts +139 -0
- package/connectors/connect-googledrive/src/api/client.ts +34 -0
- package/connectors/connect-googledrive/src/api/index.ts +12 -0
- package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
- package/connectors/connect-googledrive/src/cli/index.ts +624 -0
- package/connectors/connect-googledrive/src/index.ts +2 -0
- package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
- package/connectors/connect-googlephotos/src/api/index.ts +8 -0
- package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
- package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
- package/connectors/connect-googletasks/src/api/index.ts +1 -0
- package/connectors/connect-googletasks/src/cli/index.ts +178 -0
- package/connectors/connect-linear/src/api/bulk.ts +219 -0
- package/connectors/connect-linear/src/api/index.ts +4 -0
- package/connectors/connect-linear/src/cli/index.ts +116 -0
- package/connectors/connect-shopify/src/api/bulk.ts +288 -0
- package/connectors/connect-shopify/src/api/index.ts +4 -0
- package/connectors/connect-shopify/src/cli/index.ts +108 -0
- package/connectors/connect-slack/src/api/bulk.ts +237 -0
- package/connectors/connect-slack/src/api/index.ts +4 -0
- package/connectors/connect-slack/src/cli/index.ts +157 -0
- package/connectors/connect-stripe/src/api/bulk.ts +488 -0
- package/connectors/connect-stripe/src/api/index.ts +4 -0
- package/connectors/connect-stripe/src/cli/index.ts +206 -0
- package/dist/index.js +75 -33
- package/dist/lib/runner.d.ts +15 -5
- package/package.json +2 -2
- package/connectors/connect-cloudflare/bun.lock +0 -32
- package/connectors/connect-gmail/bin/index.js +0 -7027
- package/connectors/connect-gmail/dist/cli/index.js +0 -7035
- package/connectors/connect-gmail/dist/index.js +0 -2174
- package/dist/server/server.test.d.ts +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { GitHubClient } from './client';
|
|
2
|
+
|
|
3
|
+
// ============================================
|
|
4
|
+
// Bulk Operation Types
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
export interface BulkOperationOptions {
|
|
8
|
+
/** Owner of the repository */
|
|
9
|
+
owner: string;
|
|
10
|
+
/** Repository name */
|
|
11
|
+
repo: string;
|
|
12
|
+
/** Maximum concurrent API calls (default: 10) */
|
|
13
|
+
concurrency?: number;
|
|
14
|
+
/** Dry run - don't actually modify */
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
/** Progress callback */
|
|
17
|
+
onProgress?: (current: number, total: number, item: unknown) => void;
|
|
18
|
+
/** Error callback */
|
|
19
|
+
onError?: (error: Error, item: unknown) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --- Issue Bulk Operations ---
|
|
23
|
+
|
|
24
|
+
export interface BulkIssueOptions extends BulkOperationOptions {
|
|
25
|
+
issueNumbers: number[];
|
|
26
|
+
/** Action to perform */
|
|
27
|
+
action: 'close' | 'reopen' | 'lock' | 'unlock';
|
|
28
|
+
/** Lock reason (for lock action only) */
|
|
29
|
+
lockReason?: 'off-topic' | 'too_heated' | 'resolved' | 'spam';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BulkIssueResult {
|
|
33
|
+
total: number;
|
|
34
|
+
success: number;
|
|
35
|
+
failed: number;
|
|
36
|
+
errors: Array<{ issueNumber: number; error: string }>;
|
|
37
|
+
results: Array<{ issueNumber: number; response: unknown }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Label Bulk Operations ---
|
|
41
|
+
|
|
42
|
+
export interface BulkLabelOptions extends BulkOperationOptions {
|
|
43
|
+
issueNumber: number;
|
|
44
|
+
/** Labels to add or remove */
|
|
45
|
+
labels: string[];
|
|
46
|
+
/** Action to perform */
|
|
47
|
+
action: 'add' | 'remove';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface BulkLabelResult {
|
|
51
|
+
total: number;
|
|
52
|
+
success: number;
|
|
53
|
+
failed: number;
|
|
54
|
+
errors: Array<{ label: string; error: string }>;
|
|
55
|
+
results: Array<{ label: string; response: unknown }>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- Pull Request Bulk Operations ---
|
|
59
|
+
|
|
60
|
+
export interface BulkPullRequestOptions extends BulkOperationOptions {
|
|
61
|
+
pullNumbers: number[];
|
|
62
|
+
/** Action to perform */
|
|
63
|
+
action: 'merge' | 'close' | 'update_branch';
|
|
64
|
+
/** Merge method (for merge action) */
|
|
65
|
+
mergeMethod?: 'merge' | 'squash' | 'rebase';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface BulkPullRequestResult {
|
|
69
|
+
total: number;
|
|
70
|
+
success: number;
|
|
71
|
+
failed: number;
|
|
72
|
+
errors: Array<{ pullNumber: number; error: string }>;
|
|
73
|
+
results: Array<{ pullNumber: number; response: unknown }>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Comment Bulk Operations ---
|
|
77
|
+
|
|
78
|
+
export interface BulkCommentOptions extends BulkOperationOptions {
|
|
79
|
+
/** Comment IDs to operate on */
|
|
80
|
+
commentIds: number[];
|
|
81
|
+
/** Action to perform */
|
|
82
|
+
action: 'delete';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface BulkCommentResult {
|
|
86
|
+
total: number;
|
|
87
|
+
success: number;
|
|
88
|
+
failed: number;
|
|
89
|
+
errors: Array<{ commentId: number; error: string }>;
|
|
90
|
+
results: Array<{ commentId: number; response: unknown }>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================
|
|
94
|
+
// Bulk Operations API
|
|
95
|
+
// ============================================
|
|
96
|
+
|
|
97
|
+
export class BulkApi {
|
|
98
|
+
private readonly client: GitHubClient;
|
|
99
|
+
|
|
100
|
+
constructor(client: GitHubClient) {
|
|
101
|
+
this.client = client;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================
|
|
105
|
+
// Bulk Issue Operations
|
|
106
|
+
// ============================================
|
|
107
|
+
|
|
108
|
+
async issues(options: BulkIssueOptions): Promise<BulkIssueResult> {
|
|
109
|
+
const { owner, repo, issueNumbers, action, lockReason, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
110
|
+
|
|
111
|
+
const result: BulkIssueResult = {
|
|
112
|
+
total: issueNumbers.length,
|
|
113
|
+
success: 0,
|
|
114
|
+
failed: 0,
|
|
115
|
+
errors: [],
|
|
116
|
+
results: [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (issueNumbers.length === 0) return result;
|
|
120
|
+
|
|
121
|
+
const chunks = this.chunkArray(issueNumbers, concurrency);
|
|
122
|
+
|
|
123
|
+
for (const chunk of chunks) {
|
|
124
|
+
await Promise.all(
|
|
125
|
+
chunk.map(async (issueNumber) => {
|
|
126
|
+
try {
|
|
127
|
+
if (dryRun) {
|
|
128
|
+
result.success++;
|
|
129
|
+
} else {
|
|
130
|
+
let response: unknown;
|
|
131
|
+
if (action === 'close') {
|
|
132
|
+
response = await this.client.patch(`/repos/${owner}/${repo}/issues/${issueNumber}`, { state: 'closed' });
|
|
133
|
+
} else if (action === 'reopen') {
|
|
134
|
+
response = await this.client.patch(`/repos/${owner}/${repo}/issues/${issueNumber}`, { state: 'open' });
|
|
135
|
+
} else if (action === 'lock') {
|
|
136
|
+
await this.client.put(`/repos/${owner}/${repo}/issues/${issueNumber}/lock`, {
|
|
137
|
+
lock_reason: lockReason,
|
|
138
|
+
});
|
|
139
|
+
response = { locked: true };
|
|
140
|
+
} else {
|
|
141
|
+
await this.client.delete(`/repos/${owner}/${repo}/issues/${issueNumber}/lock`);
|
|
142
|
+
response = { locked: false };
|
|
143
|
+
}
|
|
144
|
+
result.success++;
|
|
145
|
+
result.results.push({ issueNumber, response });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, issueNumber);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
result.failed++;
|
|
151
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
152
|
+
result.errors.push({ issueNumber, error: errorMessage });
|
|
153
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), issueNumber);
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============================================
|
|
163
|
+
// Bulk Label Operations
|
|
164
|
+
// ============================================
|
|
165
|
+
|
|
166
|
+
async labels(options: BulkLabelOptions): Promise<BulkLabelResult> {
|
|
167
|
+
const { owner, repo, issueNumber, labels, action, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
168
|
+
|
|
169
|
+
const result: BulkLabelResult = {
|
|
170
|
+
total: labels.length,
|
|
171
|
+
success: 0,
|
|
172
|
+
failed: 0,
|
|
173
|
+
errors: [],
|
|
174
|
+
results: [],
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (labels.length === 0) return result;
|
|
178
|
+
|
|
179
|
+
const chunks = this.chunkArray(labels, concurrency);
|
|
180
|
+
|
|
181
|
+
for (const chunk of chunks) {
|
|
182
|
+
await Promise.all(
|
|
183
|
+
chunk.map(async (label) => {
|
|
184
|
+
try {
|
|
185
|
+
if (dryRun) {
|
|
186
|
+
result.success++;
|
|
187
|
+
} else {
|
|
188
|
+
let response: unknown;
|
|
189
|
+
if (action === 'add') {
|
|
190
|
+
await this.client.post(`/repos/${owner}/${repo}/issues/${issueNumber}/labels`, {
|
|
191
|
+
labels: [label],
|
|
192
|
+
});
|
|
193
|
+
response = { added: label };
|
|
194
|
+
} else {
|
|
195
|
+
await this.client.delete(`/repos/${owner}/${repo}/issues/${issueNumber}/labels/${encodeURIComponent(label)}`);
|
|
196
|
+
response = { removed: label };
|
|
197
|
+
}
|
|
198
|
+
result.success++;
|
|
199
|
+
result.results.push({ label, response });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, label);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
result.failed++;
|
|
205
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
206
|
+
result.errors.push({ label, error: errorMessage });
|
|
207
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), label);
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================================
|
|
217
|
+
// Bulk Pull Request Operations
|
|
218
|
+
// ============================================
|
|
219
|
+
|
|
220
|
+
async pullRequests(options: BulkPullRequestOptions): Promise<BulkPullRequestResult> {
|
|
221
|
+
const { owner, repo, pullNumbers, action, mergeMethod = 'merge', concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
222
|
+
|
|
223
|
+
const result: BulkPullRequestResult = {
|
|
224
|
+
total: pullNumbers.length,
|
|
225
|
+
success: 0,
|
|
226
|
+
failed: 0,
|
|
227
|
+
errors: [],
|
|
228
|
+
results: [],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (pullNumbers.length === 0) return result;
|
|
232
|
+
|
|
233
|
+
const chunks = this.chunkArray(pullNumbers, concurrency);
|
|
234
|
+
|
|
235
|
+
for (const chunk of chunks) {
|
|
236
|
+
await Promise.all(
|
|
237
|
+
chunk.map(async (pullNumber) => {
|
|
238
|
+
try {
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
result.success++;
|
|
241
|
+
} else {
|
|
242
|
+
let response: unknown;
|
|
243
|
+
if (action === 'merge') {
|
|
244
|
+
response = await this.client.put(`/repos/${owner}/${repo}/pulls/${pullNumber}/merge`, {
|
|
245
|
+
merge_method: mergeMethod,
|
|
246
|
+
});
|
|
247
|
+
} else if (action === 'close') {
|
|
248
|
+
response = await this.client.patch(`/repos/${owner}/${repo}/pulls/${pullNumber}`, { state: 'closed' });
|
|
249
|
+
} else {
|
|
250
|
+
response = await this.client.put(`/repos/${owner}/${repo}/pulls/${pullNumber}/update-branch`);
|
|
251
|
+
}
|
|
252
|
+
result.success++;
|
|
253
|
+
result.results.push({ pullNumber, response });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, pullNumber);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
result.failed++;
|
|
259
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
260
|
+
result.errors.push({ pullNumber, error: errorMessage });
|
|
261
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), pullNumber);
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ============================================
|
|
271
|
+
// Bulk Comment Operations
|
|
272
|
+
// ============================================
|
|
273
|
+
|
|
274
|
+
async comments(options: BulkCommentOptions): Promise<BulkCommentResult> {
|
|
275
|
+
const { owner, repo, commentIds, action, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
276
|
+
|
|
277
|
+
const result: BulkCommentResult = {
|
|
278
|
+
total: commentIds.length,
|
|
279
|
+
success: 0,
|
|
280
|
+
failed: 0,
|
|
281
|
+
errors: [],
|
|
282
|
+
results: [],
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
if (commentIds.length === 0) return result;
|
|
286
|
+
|
|
287
|
+
const chunks = this.chunkArray(commentIds, concurrency);
|
|
288
|
+
|
|
289
|
+
for (const chunk of chunks) {
|
|
290
|
+
await Promise.all(
|
|
291
|
+
chunk.map(async (commentId) => {
|
|
292
|
+
try {
|
|
293
|
+
if (dryRun) {
|
|
294
|
+
result.success++;
|
|
295
|
+
} else {
|
|
296
|
+
if (action === 'delete') {
|
|
297
|
+
await this.client.delete(`/repos/${owner}/${repo}/issues/comments/${commentId}`);
|
|
298
|
+
}
|
|
299
|
+
result.success++;
|
|
300
|
+
result.results.push({ commentId, response: { deleted: commentId } });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, commentId);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
result.failed++;
|
|
306
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
307
|
+
result.errors.push({ commentId, error: errorMessage });
|
|
308
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), commentId);
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================
|
|
318
|
+
// Helper Methods
|
|
319
|
+
// ============================================
|
|
320
|
+
|
|
321
|
+
private chunkArray<T>(array: T[], size: number): T[][] {
|
|
322
|
+
const chunks: T[][] = [];
|
|
323
|
+
for (let i = 0; i < array.length; i += size) {
|
|
324
|
+
chunks.push(array.slice(i, i + size));
|
|
325
|
+
}
|
|
326
|
+
return chunks;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -4,6 +4,7 @@ import { ReposApi } from './repos';
|
|
|
4
4
|
import { IssuesApi } from './issues';
|
|
5
5
|
import { PullsApi } from './pulls';
|
|
6
6
|
import { UsersApi } from './users';
|
|
7
|
+
import { BulkApi } from './bulk';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Main GitHub connector class
|
|
@@ -16,6 +17,7 @@ export class GitHub {
|
|
|
16
17
|
public readonly issues: IssuesApi;
|
|
17
18
|
public readonly pulls: PullsApi;
|
|
18
19
|
public readonly users: UsersApi;
|
|
20
|
+
public readonly bulk: BulkApi;
|
|
19
21
|
|
|
20
22
|
constructor(config: GitHubConfig) {
|
|
21
23
|
this.client = new GitHubClient(config);
|
|
@@ -23,6 +25,7 @@ export class GitHub {
|
|
|
23
25
|
this.issues = new IssuesApi(this.client);
|
|
24
26
|
this.pulls = new PullsApi(this.client);
|
|
25
27
|
this.users = new UsersApi(this.client);
|
|
28
|
+
this.bulk = new BulkApi(this.client);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -60,3 +63,4 @@ export { ReposApi } from './repos';
|
|
|
60
63
|
export { IssuesApi } from './issues';
|
|
61
64
|
export { PullsApi } from './pulls';
|
|
62
65
|
export { UsersApi } from './users';
|
|
66
|
+
export { BulkApi } from './bulk';
|
|
@@ -830,5 +830,150 @@ userCmd
|
|
|
830
830
|
}
|
|
831
831
|
});
|
|
832
832
|
|
|
833
|
+
// ============================================
|
|
834
|
+
// Bulk Operations Commands
|
|
835
|
+
// ============================================
|
|
836
|
+
const bulkCmd = program
|
|
837
|
+
.command('bulk')
|
|
838
|
+
.description('Bulk operations');
|
|
839
|
+
|
|
840
|
+
bulkCmd
|
|
841
|
+
.command('issues')
|
|
842
|
+
.description('Bulk issue operations')
|
|
843
|
+
.requiredOption('--owner <owner>', 'Repository owner')
|
|
844
|
+
.requiredOption('--repo <repo>', 'Repository name')
|
|
845
|
+
.requiredOption('--numbers <numbers>', 'Comma-separated issue numbers')
|
|
846
|
+
.requiredOption('--action <action>', 'Action: close, reopen, lock, unlock')
|
|
847
|
+
.option('--lock-reason <reason>', 'Lock reason: off-topic, too_heated, resolved, spam')
|
|
848
|
+
.option('--concurrency <number>', 'Max concurrent operations', '10')
|
|
849
|
+
.option('--dry-run', 'Preview without executing')
|
|
850
|
+
.action(async function(this: Command, opts) {
|
|
851
|
+
try {
|
|
852
|
+
const client = getClient();
|
|
853
|
+
const result = await client.bulk.issues({
|
|
854
|
+
owner: opts.owner,
|
|
855
|
+
repo: opts.repo,
|
|
856
|
+
issueNumbers: opts.numbers.split(',').map((n: string) => parseInt(n.trim())),
|
|
857
|
+
action: opts.action as 'close' | 'reopen' | 'lock' | 'unlock',
|
|
858
|
+
lockReason: opts.lockReason,
|
|
859
|
+
concurrency: parseInt(opts.concurrency),
|
|
860
|
+
dryRun: opts.dryRun,
|
|
861
|
+
onProgress: (current, total) => info(`Progress: ${current}/${total}`),
|
|
862
|
+
});
|
|
863
|
+
print(result, getFormat(this));
|
|
864
|
+
info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
|
|
865
|
+
if (result.errors.length > 0) {
|
|
866
|
+
warn('Errors:');
|
|
867
|
+
result.errors.forEach(e => error(` #${e.issueNumber}: ${e.error}`));
|
|
868
|
+
}
|
|
869
|
+
} catch (err) {
|
|
870
|
+
error(String(err));
|
|
871
|
+
process.exit(1);
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
bulkCmd
|
|
876
|
+
.command('labels')
|
|
877
|
+
.description('Bulk label operations')
|
|
878
|
+
.requiredOption('--owner <owner>', 'Repository owner')
|
|
879
|
+
.requiredOption('--repo <repo>', 'Repository name')
|
|
880
|
+
.requiredOption('--issue-number <number>', 'Issue number')
|
|
881
|
+
.requiredOption('--labels <labels>', 'Comma-separated label names')
|
|
882
|
+
.requiredOption('--action <action>', 'Action: add, remove')
|
|
883
|
+
.option('--concurrency <number>', 'Max concurrent operations', '10')
|
|
884
|
+
.option('--dry-run', 'Preview without executing')
|
|
885
|
+
.action(async function(this: Command, opts) {
|
|
886
|
+
try {
|
|
887
|
+
const client = getClient();
|
|
888
|
+
const result = await client.bulk.labels({
|
|
889
|
+
owner: opts.owner,
|
|
890
|
+
repo: opts.repo,
|
|
891
|
+
issueNumber: parseInt(opts.issueNumber),
|
|
892
|
+
labels: opts.labels.split(',').map((l: string) => l.trim()),
|
|
893
|
+
action: opts.action as 'add' | 'remove',
|
|
894
|
+
concurrency: parseInt(opts.concurrency),
|
|
895
|
+
dryRun: opts.dryRun,
|
|
896
|
+
onProgress: (current, total) => info(`Progress: ${current}/${total}`),
|
|
897
|
+
});
|
|
898
|
+
print(result, getFormat(this));
|
|
899
|
+
info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
|
|
900
|
+
if (result.errors.length > 0) {
|
|
901
|
+
warn('Errors:');
|
|
902
|
+
result.errors.forEach(e => error(` ${e.label}: ${e.error}`));
|
|
903
|
+
}
|
|
904
|
+
} catch (err) {
|
|
905
|
+
error(String(err));
|
|
906
|
+
process.exit(1);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
bulkCmd
|
|
911
|
+
.command('pull-requests')
|
|
912
|
+
.description('Bulk pull request operations')
|
|
913
|
+
.requiredOption('--owner <owner>', 'Repository owner')
|
|
914
|
+
.requiredOption('--repo <repo>', 'Repository name')
|
|
915
|
+
.requiredOption('--numbers <numbers>', 'Comma-separated PR numbers')
|
|
916
|
+
.requiredOption('--action <action>', 'Action: merge, close, update_branch')
|
|
917
|
+
.option('--merge-method <method>', 'Merge method: merge, squash, rebase', 'merge')
|
|
918
|
+
.option('--concurrency <number>', 'Max concurrent operations', '10')
|
|
919
|
+
.option('--dry-run', 'Preview without executing')
|
|
920
|
+
.action(async function(this: Command, opts) {
|
|
921
|
+
try {
|
|
922
|
+
const client = getClient();
|
|
923
|
+
const result = await client.bulk.pullRequests({
|
|
924
|
+
owner: opts.owner,
|
|
925
|
+
repo: opts.repo,
|
|
926
|
+
pullNumbers: opts.numbers.split(',').map((n: string) => parseInt(n.trim())),
|
|
927
|
+
action: opts.action as 'merge' | 'close' | 'update_branch',
|
|
928
|
+
mergeMethod: opts.mergeMethod as 'merge' | 'squash' | 'rebase',
|
|
929
|
+
concurrency: parseInt(opts.concurrency),
|
|
930
|
+
dryRun: opts.dryRun,
|
|
931
|
+
onProgress: (current, total) => info(`Progress: ${current}/${total}`),
|
|
932
|
+
});
|
|
933
|
+
print(result, getFormat(this));
|
|
934
|
+
info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
|
|
935
|
+
if (result.errors.length > 0) {
|
|
936
|
+
warn('Errors:');
|
|
937
|
+
result.errors.forEach(e => error(` #${e.pullNumber}: ${e.error}`));
|
|
938
|
+
}
|
|
939
|
+
} catch (err) {
|
|
940
|
+
error(String(err));
|
|
941
|
+
process.exit(1);
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
bulkCmd
|
|
946
|
+
.command('comments')
|
|
947
|
+
.description('Bulk comment operations')
|
|
948
|
+
.requiredOption('--owner <owner>', 'Repository owner')
|
|
949
|
+
.requiredOption('--repo <repo>', 'Repository name')
|
|
950
|
+
.requiredOption('--ids <ids>', 'Comma-separated comment IDs')
|
|
951
|
+
.requiredOption('--action <action>', 'Action: delete')
|
|
952
|
+
.option('--concurrency <number>', 'Max concurrent operations', '10')
|
|
953
|
+
.option('--dry-run', 'Preview without executing')
|
|
954
|
+
.action(async function(this: Command, opts) {
|
|
955
|
+
try {
|
|
956
|
+
const client = getClient();
|
|
957
|
+
const result = await client.bulk.comments({
|
|
958
|
+
owner: opts.owner,
|
|
959
|
+
repo: opts.repo,
|
|
960
|
+
commentIds: opts.ids.split(',').map((n: string) => parseInt(n.trim())),
|
|
961
|
+
action: opts.action as 'delete',
|
|
962
|
+
concurrency: parseInt(opts.concurrency),
|
|
963
|
+
dryRun: opts.dryRun,
|
|
964
|
+
onProgress: (current, total) => info(`Progress: ${current}/${total}`),
|
|
965
|
+
});
|
|
966
|
+
print(result, getFormat(this));
|
|
967
|
+
info(`Success: ${result.success}, Failed: ${result.failed}, Total: ${result.total}`);
|
|
968
|
+
if (result.errors.length > 0) {
|
|
969
|
+
warn('Errors:');
|
|
970
|
+
result.errors.forEach(e => error(` comment ${e.commentId}: ${e.error}`));
|
|
971
|
+
}
|
|
972
|
+
} catch (err) {
|
|
973
|
+
error(String(err));
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
|
|
833
978
|
// Parse and execute
|
|
834
979
|
program.parse();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { GmailClient } from './client';
|
|
2
|
+
|
|
3
|
+
// ============================================
|
|
4
|
+
// History API Types
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
export interface ListHistoryOptions {
|
|
8
|
+
startHistoryId: string;
|
|
9
|
+
historyTypes?: ('messageAdded' | 'messageChanged' | 'messageDeleted' | 'labelAdded' | 'labelRemoved')[];
|
|
10
|
+
labelId?: string;
|
|
11
|
+
maxResults?: number;
|
|
12
|
+
pageToken?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HistoryRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
messages?: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
threadId: string;
|
|
20
|
+
}>;
|
|
21
|
+
messagesAdded?: Array<{
|
|
22
|
+
message: { id: string; threadId: string };
|
|
23
|
+
}>;
|
|
24
|
+
messagesDeleted?: Array<{
|
|
25
|
+
message: { id: string; threadId: string };
|
|
26
|
+
}>;
|
|
27
|
+
labelsAdded?: Array<{
|
|
28
|
+
message: { id: string; threadId: string };
|
|
29
|
+
labelIds: string[];
|
|
30
|
+
}>;
|
|
31
|
+
labelsRemoved?: Array<{
|
|
32
|
+
message: { id: string; threadId: string };
|
|
33
|
+
labelIds: string[];
|
|
34
|
+
}>;
|
|
35
|
+
messageChanged?: Array<{
|
|
36
|
+
message: { id: string; threadId: string };
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ListHistoryResponse {
|
|
41
|
+
history?: HistoryRecord[];
|
|
42
|
+
nextPageToken?: string;
|
|
43
|
+
historyId: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* History API module - retrieve mailbox change history
|
|
48
|
+
*/
|
|
49
|
+
export class HistoryApi {
|
|
50
|
+
constructor(private readonly client: GmailClient) {}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* List the history of all changes to the user's mailbox
|
|
54
|
+
*/
|
|
55
|
+
async list(options: ListHistoryOptions): Promise<ListHistoryResponse> {
|
|
56
|
+
const params: Record<string, string | number> = {
|
|
57
|
+
startHistoryId: options.startHistoryId,
|
|
58
|
+
};
|
|
59
|
+
if (options.historyTypes && options.historyTypes.length > 0) {
|
|
60
|
+
params.historyTypes = options.historyTypes.join(',');
|
|
61
|
+
}
|
|
62
|
+
if (options.labelId) params.labelId = options.labelId;
|
|
63
|
+
if (options.maxResults) params.maxResults = options.maxResults;
|
|
64
|
+
if (options.pageToken) params.pageToken = options.pageToken;
|
|
65
|
+
|
|
66
|
+
return this.client.get<ListHistoryResponse>('/v1/users/me/history', params);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Iterate over all history records (handles pagination)
|
|
71
|
+
*/
|
|
72
|
+
async *listAll(options: Omit<ListHistoryOptions, 'pageToken'>): AsyncGenerator<HistoryRecord> {
|
|
73
|
+
let pageToken: string | undefined;
|
|
74
|
+
do {
|
|
75
|
+
const response = await this.list({ ...options, pageToken });
|
|
76
|
+
for (const record of response.history || []) {
|
|
77
|
+
yield record;
|
|
78
|
+
}
|
|
79
|
+
pageToken = response.nextPageToken;
|
|
80
|
+
} while (pageToken);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -8,6 +8,9 @@ import { FiltersApi } from './filters';
|
|
|
8
8
|
import { AttachmentsApi } from './attachments';
|
|
9
9
|
import { ExportApi } from './export';
|
|
10
10
|
import { BulkApi } from './bulk';
|
|
11
|
+
import { HistoryApi } from './history';
|
|
12
|
+
import { SettingsApi } from './settings';
|
|
13
|
+
import { WatchApi } from './watch';
|
|
11
14
|
import { refreshTokens } from '../utils/auth';
|
|
12
15
|
|
|
13
16
|
/** Tokens passed to Gmail.createWithTokens() */
|
|
@@ -33,6 +36,9 @@ export class Gmail {
|
|
|
33
36
|
public readonly attachments: AttachmentsApi;
|
|
34
37
|
public readonly export: ExportApi;
|
|
35
38
|
public readonly bulk: BulkApi;
|
|
39
|
+
public readonly history: HistoryApi;
|
|
40
|
+
public readonly settings: SettingsApi;
|
|
41
|
+
public readonly watch: WatchApi;
|
|
36
42
|
|
|
37
43
|
constructor(client?: GmailClient) {
|
|
38
44
|
this.client = client ?? new GmailClient();
|
|
@@ -45,6 +51,9 @@ export class Gmail {
|
|
|
45
51
|
this.attachments = new AttachmentsApi(this.client);
|
|
46
52
|
this.export = new ExportApi(this.client);
|
|
47
53
|
this.bulk = new BulkApi(this.client);
|
|
54
|
+
this.history = new HistoryApi(this.client);
|
|
55
|
+
this.settings = new SettingsApi(this.client);
|
|
56
|
+
this.watch = new WatchApi(this.client);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/**
|
|
@@ -156,3 +165,6 @@ export { FiltersApi } from './filters';
|
|
|
156
165
|
export { AttachmentsApi } from './attachments';
|
|
157
166
|
export { ExportApi } from './export';
|
|
158
167
|
export { BulkApi } from './bulk';
|
|
168
|
+
export { HistoryApi } from './history';
|
|
169
|
+
export { SettingsApi } from './settings';
|
|
170
|
+
export { WatchApi } from './watch';
|