@plosson/agentio 0.5.10 → 0.5.12
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/package.json +1 -1
- package/src/commands/gmail.ts +93 -85
- package/src/services/gmail/client.ts +89 -66
- package/src/types/gmail.ts +1 -6
- package/src/utils/output.ts +7 -0
package/package.json
CHANGED
package/src/commands/gmail.ts
CHANGED
|
@@ -7,11 +7,94 @@ import { setProfile, getProfile } from '../config/config-manager';
|
|
|
7
7
|
import { createProfileCommands } from '../utils/profile-commands';
|
|
8
8
|
import { performOAuthFlow } from '../auth/oauth';
|
|
9
9
|
import { GmailClient } from '../services/gmail/client';
|
|
10
|
-
import { printMessageList, printMessage, printSendResult, printArchived, printMarked, printAttachmentList, printAttachmentDownloaded, raw } from '../utils/output';
|
|
10
|
+
import { printMessageList, printMessage, printSendResult, printDraftResult, printArchived, printMarked, printAttachmentList, printAttachmentDownloaded, raw } from '../utils/output';
|
|
11
11
|
import { CliError, handleError } from '../utils/errors';
|
|
12
12
|
import { readStdin } from '../utils/stdin';
|
|
13
13
|
import { enforceWriteAccess } from '../utils/read-only';
|
|
14
|
-
import type { GmailAttachment } from '../types/gmail';
|
|
14
|
+
import type { GmailAttachment, GmailSendOptions } from '../types/gmail';
|
|
15
|
+
|
|
16
|
+
function addComposeOptions(cmd: Command): Command {
|
|
17
|
+
return cmd
|
|
18
|
+
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
19
|
+
.option('--to <email>', 'Recipient (repeatable, required unless --reply-to)', (val: string, acc: string[]) => [...acc, val], [])
|
|
20
|
+
.option('--cc <email>', 'CC recipient (repeatable)', (val: string, acc: string[]) => [...acc, val], [])
|
|
21
|
+
.option('--bcc <email>', 'BCC recipient (repeatable)', (val: string, acc: string[]) => [...acc, val], [])
|
|
22
|
+
.option('--subject <subject>', 'Email subject (required unless --reply-to)')
|
|
23
|
+
.option('--body <body>', 'Email body (or pipe via stdin)')
|
|
24
|
+
.option('--html', 'Treat body as HTML')
|
|
25
|
+
.option('--reply-to <thread-id>', 'Thread ID to reply to (derives to/subject from thread)')
|
|
26
|
+
.option('--attachment <path>', 'File to attach (repeatable)', (val: string, acc: string[]) => [...acc, val], [])
|
|
27
|
+
.option('--inline <cid:path>', 'Inline image (repeatable, format: contentId:filepath). Supports PNG, JPG, GIF only (not SVG)', (val: string, acc: string[]) => [...acc, val], []);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function parseBody(body: string | undefined): Promise<string> {
|
|
31
|
+
if (!body) {
|
|
32
|
+
body = await readStdin() ?? undefined;
|
|
33
|
+
}
|
|
34
|
+
if (!body) {
|
|
35
|
+
throw new CliError('INVALID_PARAMS', 'Body is required. Use --body or pipe via stdin.');
|
|
36
|
+
}
|
|
37
|
+
return body;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseAttachments(paths: string[]): GmailAttachment[] {
|
|
41
|
+
return paths.map((path: string) => ({
|
|
42
|
+
path,
|
|
43
|
+
filename: basename(path),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseInlineAttachments(specs: string[]): GmailAttachment[] {
|
|
48
|
+
return specs.map((spec: string) => {
|
|
49
|
+
const colonIndex = spec.indexOf(':');
|
|
50
|
+
if (colonIndex === -1) {
|
|
51
|
+
throw new CliError('INVALID_PARAMS', `Invalid inline format: ${spec}`, 'Use format: contentId:filepath (e.g., logo:./logo.png)');
|
|
52
|
+
}
|
|
53
|
+
const contentId = spec.substring(0, colonIndex);
|
|
54
|
+
const path = spec.substring(colonIndex + 1);
|
|
55
|
+
return {
|
|
56
|
+
path,
|
|
57
|
+
filename: basename(path),
|
|
58
|
+
contentId,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function parseSendOptions(options: Record<string, unknown>): Promise<GmailSendOptions> {
|
|
64
|
+
const replyTo = options.replyTo as string | undefined;
|
|
65
|
+
const to = options.to as string[];
|
|
66
|
+
const subject = options.subject as string | undefined;
|
|
67
|
+
|
|
68
|
+
if (!replyTo) {
|
|
69
|
+
if (!to.length) {
|
|
70
|
+
throw new CliError('INVALID_PARAMS', '--to is required (unless using --reply-to)');
|
|
71
|
+
}
|
|
72
|
+
if (!subject) {
|
|
73
|
+
throw new CliError('INVALID_PARAMS', '--subject is required (unless using --reply-to)');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const body = await parseBody(options.body as string | undefined);
|
|
78
|
+
|
|
79
|
+
const regularAttachments = parseAttachments(options.attachment as string[]);
|
|
80
|
+
const inlineAttachments = parseInlineAttachments(options.inline as string[]);
|
|
81
|
+
|
|
82
|
+
const attachments: GmailAttachment[] | undefined =
|
|
83
|
+
regularAttachments.length || inlineAttachments.length
|
|
84
|
+
? [...regularAttachments, ...inlineAttachments]
|
|
85
|
+
: undefined;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
to,
|
|
89
|
+
cc: (options.cc as string[]).length ? options.cc as string[] : undefined,
|
|
90
|
+
bcc: (options.bcc as string[]).length ? options.bcc as string[] : undefined,
|
|
91
|
+
subject: subject || '',
|
|
92
|
+
body,
|
|
93
|
+
isHtml: options.html as boolean | undefined,
|
|
94
|
+
attachments,
|
|
95
|
+
replyTo,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
15
98
|
|
|
16
99
|
function escapeHtml(text: string): string {
|
|
17
100
|
return text
|
|
@@ -176,102 +259,27 @@ Query Syntax Examples:
|
|
|
176
259
|
}
|
|
177
260
|
});
|
|
178
261
|
|
|
179
|
-
gmail
|
|
180
|
-
.command('send')
|
|
181
|
-
.description('Send an email')
|
|
182
|
-
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
183
|
-
.requiredOption('--to <email>', 'Recipient (repeatable)', (val, acc: string[]) => [...acc, val], [])
|
|
184
|
-
.option('--cc <email>', 'CC recipient (repeatable)', (val, acc: string[]) => [...acc, val], [])
|
|
185
|
-
.option('--bcc <email>', 'BCC recipient (repeatable)', (val, acc: string[]) => [...acc, val], [])
|
|
186
|
-
.requiredOption('--subject <subject>', 'Email subject')
|
|
187
|
-
.option('--body <body>', 'Email body (or pipe via stdin)')
|
|
188
|
-
.option('--html', 'Treat body as HTML')
|
|
189
|
-
.option('--attachment <path>', 'File to attach (repeatable)', (val, acc: string[]) => [...acc, val], [])
|
|
190
|
-
.option('--inline <cid:path>', 'Inline image (repeatable, format: contentId:filepath). Supports PNG, JPG, GIF only (not SVG)', (val, acc: string[]) => [...acc, val], [])
|
|
262
|
+
addComposeOptions(gmail.command('send').description('Send an email'))
|
|
191
263
|
.action(async (options) => {
|
|
192
264
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// Check for stdin if no body provided
|
|
196
|
-
if (!body) {
|
|
197
|
-
body = await readStdin();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (!body) {
|
|
201
|
-
throw new CliError('INVALID_PARAMS', 'Body is required. Use --body or pipe via stdin.');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Process regular attachments
|
|
205
|
-
const regularAttachments: GmailAttachment[] = options.attachment.map((path: string) => ({
|
|
206
|
-
path,
|
|
207
|
-
filename: basename(path),
|
|
208
|
-
}));
|
|
209
|
-
|
|
210
|
-
// Process inline images (format: contentId:filepath)
|
|
211
|
-
const inlineAttachments: GmailAttachment[] = options.inline.map((spec: string) => {
|
|
212
|
-
const colonIndex = spec.indexOf(':');
|
|
213
|
-
if (colonIndex === -1) {
|
|
214
|
-
throw new CliError('INVALID_PARAMS', `Invalid inline format: ${spec}`, 'Use format: contentId:filepath (e.g., logo:./logo.png)');
|
|
215
|
-
}
|
|
216
|
-
const contentId = spec.substring(0, colonIndex);
|
|
217
|
-
const path = spec.substring(colonIndex + 1);
|
|
218
|
-
return {
|
|
219
|
-
path,
|
|
220
|
-
filename: basename(path),
|
|
221
|
-
contentId,
|
|
222
|
-
};
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
// Combine attachments
|
|
226
|
-
const attachments: GmailAttachment[] | undefined =
|
|
227
|
-
regularAttachments.length || inlineAttachments.length
|
|
228
|
-
? [...regularAttachments, ...inlineAttachments]
|
|
229
|
-
: undefined;
|
|
230
|
-
|
|
265
|
+
const sendOptions = await parseSendOptions(options);
|
|
231
266
|
const { client, profile } = await getGmailClient(options.profile);
|
|
232
267
|
await enforceWriteAccess('gmail', profile, 'send email');
|
|
233
|
-
const result = await client.send(
|
|
234
|
-
to: options.to,
|
|
235
|
-
cc: options.cc.length ? options.cc : undefined,
|
|
236
|
-
bcc: options.bcc.length ? options.bcc : undefined,
|
|
237
|
-
subject: options.subject,
|
|
238
|
-
body,
|
|
239
|
-
isHtml: options.html,
|
|
240
|
-
attachments,
|
|
241
|
-
});
|
|
268
|
+
const result = await client.send(sendOptions);
|
|
242
269
|
printSendResult(result);
|
|
243
270
|
} catch (error) {
|
|
244
271
|
handleError(error);
|
|
245
272
|
}
|
|
246
273
|
});
|
|
247
274
|
|
|
248
|
-
gmail
|
|
249
|
-
.command('reply')
|
|
250
|
-
.description('Reply to a thread')
|
|
251
|
-
.option('--profile <name>', 'Profile name (optional if only one profile exists)')
|
|
252
|
-
.requiredOption('--thread-id <id>', 'Thread ID')
|
|
253
|
-
.option('--body <body>', 'Reply body (or pipe via stdin)')
|
|
254
|
-
.option('--html', 'Treat body as HTML')
|
|
275
|
+
addComposeOptions(gmail.command('draft').description('Create an email draft'))
|
|
255
276
|
.action(async (options) => {
|
|
256
277
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!body) {
|
|
260
|
-
body = await readStdin();
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (!body) {
|
|
264
|
-
throw new CliError('INVALID_PARAMS', 'Body is required. Use --body or pipe via stdin.');
|
|
265
|
-
}
|
|
266
|
-
|
|
278
|
+
const sendOptions = await parseSendOptions(options);
|
|
267
279
|
const { client, profile } = await getGmailClient(options.profile);
|
|
268
|
-
await enforceWriteAccess('gmail', profile, '
|
|
269
|
-
const result = await client.
|
|
270
|
-
|
|
271
|
-
body,
|
|
272
|
-
isHtml: options.html,
|
|
273
|
-
});
|
|
274
|
-
printSendResult(result);
|
|
280
|
+
await enforceWriteAccess('gmail', profile, 'create draft');
|
|
281
|
+
const result = await client.draft(sendOptions);
|
|
282
|
+
printDraftResult(result);
|
|
275
283
|
} catch (error) {
|
|
276
284
|
handleError(error);
|
|
277
285
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { gmail, type gmail_v1 } from '@googleapis/gmail';
|
|
2
2
|
import type { OAuth2Client } from 'google-auth-library';
|
|
3
3
|
import { basename } from 'path';
|
|
4
|
-
import type { GmailMessage, GmailListOptions, GmailSendOptions,
|
|
4
|
+
import type { GmailMessage, GmailListOptions, GmailSendOptions, GmailAttachment, GmailAttachmentInfo } from '../../types/gmail';
|
|
5
5
|
import type { ServiceClient, ValidationResult } from '../../types/service';
|
|
6
6
|
import { CliError } from '../../utils/errors';
|
|
7
7
|
|
|
@@ -265,14 +265,57 @@ export class GmailClient implements ServiceClient {
|
|
|
265
265
|
return this.list({ query, limit });
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
async
|
|
269
|
-
|
|
268
|
+
private async resolveReplyContext(threadId: string): Promise<{
|
|
269
|
+
to: string[];
|
|
270
|
+
subject: string;
|
|
271
|
+
extraHeaders: string[];
|
|
272
|
+
}> {
|
|
273
|
+
const thread = await this.gmail.users.threads.get({
|
|
274
|
+
userId: 'me',
|
|
275
|
+
id: threadId,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const messages = thread.data.messages || [];
|
|
279
|
+
if (messages.length === 0) {
|
|
280
|
+
throw new CliError('NOT_FOUND', `Thread not found: ${threadId}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const lastMessage = messages[messages.length - 1];
|
|
284
|
+
const headers = this.parseHeaders(lastMessage.payload?.headers);
|
|
285
|
+
|
|
286
|
+
const to = [headers['reply-to'] || headers['from'] || ''];
|
|
287
|
+
const subject = headers['subject']?.startsWith('Re:')
|
|
288
|
+
? headers['subject']
|
|
289
|
+
: `Re: ${headers['subject'] || '(no subject)'}`;
|
|
290
|
+
const messageId = headers['message-id'] || '';
|
|
291
|
+
|
|
292
|
+
const extraHeaders: string[] = [];
|
|
293
|
+
if (messageId) {
|
|
294
|
+
extraHeaders.push(`In-Reply-To: ${messageId}`);
|
|
295
|
+
extraHeaders.push(`References: ${messageId}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return { to, subject, extraHeaders };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private async buildEncodedMessage(options: GmailSendOptions): Promise<string> {
|
|
302
|
+
const { cc, bcc, body, isHtml, attachments, replyTo } = options;
|
|
270
303
|
const userEmail = await this.getUserEmail();
|
|
271
304
|
|
|
305
|
+
let to = options.to;
|
|
306
|
+
let subject = options.subject;
|
|
307
|
+
let extraHeaders: string[] | undefined;
|
|
308
|
+
|
|
309
|
+
if (replyTo) {
|
|
310
|
+
const replyContext = await this.resolveReplyContext(replyTo);
|
|
311
|
+
if (!to.length) to = replyContext.to;
|
|
312
|
+
if (!subject) subject = replyContext.subject;
|
|
313
|
+
extraHeaders = replyContext.extraHeaders;
|
|
314
|
+
}
|
|
315
|
+
|
|
272
316
|
let rawMessage: string;
|
|
273
317
|
|
|
274
318
|
if (attachments && attachments.length > 0) {
|
|
275
|
-
// Build multipart MIME message with attachments
|
|
276
319
|
rawMessage = await this.buildMultipartMessage({
|
|
277
320
|
from: userEmail,
|
|
278
321
|
to,
|
|
@@ -282,27 +325,35 @@ export class GmailClient implements ServiceClient {
|
|
|
282
325
|
body,
|
|
283
326
|
isHtml,
|
|
284
327
|
attachments,
|
|
328
|
+
extraHeaders,
|
|
285
329
|
});
|
|
286
330
|
} else {
|
|
287
|
-
// Simple message without attachments
|
|
288
331
|
rawMessage = [
|
|
289
332
|
`From: ${userEmail}`,
|
|
290
333
|
`To: ${to.join(', ')}`,
|
|
291
334
|
cc?.length ? `Cc: ${cc.join(', ')}` : null,
|
|
292
335
|
bcc?.length ? `Bcc: ${bcc.join(', ')}` : null,
|
|
293
336
|
`Subject: ${subject}`,
|
|
337
|
+
...(extraHeaders || []),
|
|
294
338
|
`Content-Type: ${isHtml ? 'text/html' : 'text/plain'}; charset=utf-8`,
|
|
295
339
|
'',
|
|
296
340
|
body,
|
|
297
341
|
].filter((line): line is string => line !== null).join('\r\n');
|
|
298
342
|
}
|
|
299
343
|
|
|
300
|
-
|
|
344
|
+
return Buffer.from(rawMessage).toString('base64url');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async send(options: GmailSendOptions): Promise<{ id: string; threadId: string; labelIds: string[] }> {
|
|
348
|
+
const encodedMessage = await this.buildEncodedMessage(options);
|
|
301
349
|
|
|
302
350
|
try {
|
|
303
351
|
const response = await this.gmail.users.messages.send({
|
|
304
352
|
userId: 'me',
|
|
305
|
-
requestBody: {
|
|
353
|
+
requestBody: {
|
|
354
|
+
raw: encodedMessage,
|
|
355
|
+
...(options.replyTo ? { threadId: options.replyTo } : {}),
|
|
356
|
+
},
|
|
306
357
|
});
|
|
307
358
|
|
|
308
359
|
return {
|
|
@@ -316,6 +367,30 @@ export class GmailClient implements ServiceClient {
|
|
|
316
367
|
}
|
|
317
368
|
}
|
|
318
369
|
|
|
370
|
+
async draft(options: GmailSendOptions): Promise<{ id: string; messageId: string }> {
|
|
371
|
+
const encodedMessage = await this.buildEncodedMessage(options);
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const response = await this.gmail.users.drafts.create({
|
|
375
|
+
userId: 'me',
|
|
376
|
+
requestBody: {
|
|
377
|
+
message: {
|
|
378
|
+
raw: encodedMessage,
|
|
379
|
+
...(options.replyTo ? { threadId: options.replyTo } : {}),
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
id: response.data.id!,
|
|
386
|
+
messageId: response.data.message?.id || '',
|
|
387
|
+
};
|
|
388
|
+
} catch (error) {
|
|
389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
+
throw new CliError('API_ERROR', `Failed to create draft: ${message}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
319
394
|
private async buildMultipartMessage(options: {
|
|
320
395
|
from: string;
|
|
321
396
|
to: string[];
|
|
@@ -325,8 +400,9 @@ export class GmailClient implements ServiceClient {
|
|
|
325
400
|
body: string;
|
|
326
401
|
isHtml?: boolean;
|
|
327
402
|
attachments: GmailAttachment[];
|
|
403
|
+
extraHeaders?: string[];
|
|
328
404
|
}): Promise<string> {
|
|
329
|
-
const { from, to, cc, bcc, subject, body, isHtml, attachments } = options;
|
|
405
|
+
const { from, to, cc, bcc, subject, body, isHtml, attachments, extraHeaders } = options;
|
|
330
406
|
|
|
331
407
|
// Separate inline and regular attachments
|
|
332
408
|
const inlineAttachments = attachments.filter(a => a.contentId);
|
|
@@ -347,6 +423,11 @@ export class GmailClient implements ServiceClient {
|
|
|
347
423
|
if (cc?.length) lines.push(`Cc: ${cc.join(', ')}`);
|
|
348
424
|
if (bcc?.length) lines.push(`Bcc: ${bcc.join(', ')}`);
|
|
349
425
|
lines.push(`Subject: ${subject}`);
|
|
426
|
+
if (extraHeaders) {
|
|
427
|
+
for (const header of extraHeaders) {
|
|
428
|
+
lines.push(header);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
350
431
|
lines.push('MIME-Version: 1.0');
|
|
351
432
|
|
|
352
433
|
if (hasRegular && hasInline) {
|
|
@@ -464,64 +545,6 @@ export class GmailClient implements ServiceClient {
|
|
|
464
545
|
}
|
|
465
546
|
}
|
|
466
547
|
|
|
467
|
-
async reply(options: GmailReplyOptions): Promise<{ id: string; threadId: string; labelIds: string[] }> {
|
|
468
|
-
const { threadId, body, isHtml } = options;
|
|
469
|
-
|
|
470
|
-
// Get the thread to find the last message
|
|
471
|
-
try {
|
|
472
|
-
const thread = await this.gmail.users.threads.get({
|
|
473
|
-
userId: 'me',
|
|
474
|
-
id: threadId,
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
const messages = thread.data.messages || [];
|
|
478
|
-
if (messages.length === 0) {
|
|
479
|
-
throw new CliError('NOT_FOUND', `Thread not found: ${threadId}`);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const lastMessage = messages[messages.length - 1];
|
|
483
|
-
const headers = this.parseHeaders(lastMessage.payload?.headers);
|
|
484
|
-
|
|
485
|
-
const userEmail = await this.getUserEmail();
|
|
486
|
-
const replyTo = headers['reply-to'] || headers['from'] || '';
|
|
487
|
-
const subject = headers['subject']?.startsWith('Re:')
|
|
488
|
-
? headers['subject']
|
|
489
|
-
: `Re: ${headers['subject'] || '(no subject)'}`;
|
|
490
|
-
const messageId = headers['message-id'] || '';
|
|
491
|
-
|
|
492
|
-
const rawHeaders = [
|
|
493
|
-
`From: ${userEmail}`,
|
|
494
|
-
`To: ${replyTo}`,
|
|
495
|
-
`Subject: ${subject}`,
|
|
496
|
-
messageId ? `In-Reply-To: ${messageId}` : '',
|
|
497
|
-
messageId ? `References: ${messageId}` : '',
|
|
498
|
-
`Content-Type: ${isHtml ? 'text/html' : 'text/plain'}; charset=utf-8`,
|
|
499
|
-
'',
|
|
500
|
-
body,
|
|
501
|
-
].filter(Boolean).join('\r\n');
|
|
502
|
-
|
|
503
|
-
const encodedMessage = Buffer.from(rawHeaders).toString('base64url');
|
|
504
|
-
|
|
505
|
-
const response = await this.gmail.users.messages.send({
|
|
506
|
-
userId: 'me',
|
|
507
|
-
requestBody: {
|
|
508
|
-
raw: encodedMessage,
|
|
509
|
-
threadId,
|
|
510
|
-
},
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
return {
|
|
514
|
-
id: response.data.id!,
|
|
515
|
-
threadId: response.data.threadId!,
|
|
516
|
-
labelIds: response.data.labelIds || ['SENT'],
|
|
517
|
-
};
|
|
518
|
-
} catch (error) {
|
|
519
|
-
if (error instanceof CliError) throw error;
|
|
520
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
521
|
-
throw new CliError('API_ERROR', `Failed to send reply: ${message}`);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
548
|
async archive(messageId: string): Promise<void> {
|
|
526
549
|
try {
|
|
527
550
|
await this.gmail.users.messages.modify({
|
package/src/types/gmail.ts
CHANGED
|
@@ -40,10 +40,5 @@ export interface GmailSendOptions {
|
|
|
40
40
|
body: string;
|
|
41
41
|
isHtml?: boolean;
|
|
42
42
|
attachments?: GmailAttachment[];
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
export interface GmailReplyOptions {
|
|
46
|
-
threadId: string;
|
|
47
|
-
body: string;
|
|
48
|
-
isHtml?: boolean;
|
|
43
|
+
replyTo?: string; // Thread ID to reply to
|
|
49
44
|
}
|
package/src/utils/output.ts
CHANGED
|
@@ -86,6 +86,13 @@ export function printSendResult(result: { id: string; threadId: string }): void
|
|
|
86
86
|
console.log(`Thread: ${result.threadId}`);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// Format draft creation result
|
|
90
|
+
export function printDraftResult(result: { id: string; messageId: string }): void {
|
|
91
|
+
console.log('Draft created');
|
|
92
|
+
console.log(`Draft ID: ${result.id}`);
|
|
93
|
+
console.log(`Message ID: ${result.messageId}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
// Format archive confirmation
|
|
90
97
|
export function printArchived(messageId: string): void {
|
|
91
98
|
console.log(`Archived: ${messageId}`);
|