@cli4ai/gmail 1.0.11 → 1.0.13

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.
@@ -0,0 +1,334 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { getGmail, output, encodeBase64Url, extractHeaders, extractBody } from './api.js';
4
+ /**
5
+ * Create multipart message with attachment
6
+ */
7
+ export function createMultipartMessage({ to, cc, bcc, subject, body, inReplyTo, references, attachments = [] }) {
8
+ const boundary = `boundary_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
9
+ const headers = [];
10
+ headers.push(`To: ${to}`);
11
+ if (cc)
12
+ headers.push(`Cc: ${cc}`);
13
+ if (bcc)
14
+ headers.push(`Bcc: ${bcc}`);
15
+ headers.push(`Subject: ${subject}`);
16
+ if (inReplyTo) {
17
+ headers.push(`In-Reply-To: ${inReplyTo}`);
18
+ headers.push(`References: ${references || inReplyTo}`);
19
+ }
20
+ headers.push('MIME-Version: 1.0');
21
+ headers.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
22
+ const parts = [];
23
+ // Text body part
24
+ parts.push(`--${boundary}`);
25
+ parts.push('Content-Type: text/plain; charset=utf-8');
26
+ parts.push('Content-Transfer-Encoding: 7bit');
27
+ parts.push('');
28
+ parts.push(body);
29
+ // Attachment parts
30
+ for (const att of attachments) {
31
+ const filename = path.basename(att.path);
32
+ const content = fs.readFileSync(att.path);
33
+ const base64Content = content.toString('base64');
34
+ const mimeType = att.mimeType || getMimeType(filename);
35
+ parts.push(`--${boundary}`);
36
+ parts.push(`Content-Type: ${mimeType}; name="${filename}"`);
37
+ parts.push('Content-Transfer-Encoding: base64');
38
+ parts.push(`Content-Disposition: attachment; filename="${filename}"`);
39
+ parts.push('');
40
+ // Split base64 into lines of 76 chars
41
+ for (let i = 0; i < base64Content.length; i += 76) {
42
+ parts.push(base64Content.slice(i, i + 76));
43
+ }
44
+ }
45
+ parts.push(`--${boundary}--`);
46
+ const message = headers.join('\r\n') + '\r\n\r\n' + parts.join('\r\n');
47
+ return encodeBase64Url(message);
48
+ }
49
+ /**
50
+ * Simple message without attachments
51
+ */
52
+ export function createSimpleMessage({ to, cc, bcc, subject, body, inReplyTo, references }) {
53
+ const lines = [];
54
+ lines.push(`To: ${to}`);
55
+ if (cc)
56
+ lines.push(`Cc: ${cc}`);
57
+ if (bcc)
58
+ lines.push(`Bcc: ${bcc}`);
59
+ lines.push(`Subject: ${subject}`);
60
+ lines.push('Content-Type: text/plain; charset=utf-8');
61
+ if (inReplyTo) {
62
+ lines.push(`In-Reply-To: ${inReplyTo}`);
63
+ lines.push(`References: ${references || inReplyTo}`);
64
+ }
65
+ lines.push('');
66
+ lines.push(body);
67
+ return encodeBase64Url(lines.join('\r\n'));
68
+ }
69
+ /**
70
+ * Get MIME type from filename
71
+ */
72
+ function getMimeType(filename) {
73
+ const ext = path.extname(filename).toLowerCase();
74
+ const mimeTypes = {
75
+ '.pdf': 'application/pdf',
76
+ '.doc': 'application/msword',
77
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
78
+ '.xls': 'application/vnd.ms-excel',
79
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
80
+ '.png': 'image/png',
81
+ '.jpg': 'image/jpeg',
82
+ '.jpeg': 'image/jpeg',
83
+ '.gif': 'image/gif',
84
+ '.txt': 'text/plain',
85
+ '.csv': 'text/csv',
86
+ '.zip': 'application/zip',
87
+ '.json': 'application/json',
88
+ '.xml': 'application/xml',
89
+ '.html': 'text/html'
90
+ };
91
+ return mimeTypes[ext] || 'application/octet-stream';
92
+ }
93
+ /**
94
+ * List all drafts
95
+ */
96
+ export async function list(options = {}) {
97
+ const gmail = await getGmail();
98
+ const limit = options.limit || 20;
99
+ const res = await gmail.users.drafts.list({
100
+ userId: 'me',
101
+ maxResults: limit
102
+ });
103
+ if (!res.data.drafts || res.data.drafts.length === 0) {
104
+ output([], options);
105
+ return [];
106
+ }
107
+ // Get details for each draft
108
+ const drafts = await Promise.all(res.data.drafts.map(async (draft) => {
109
+ const draftRes = await gmail.users.drafts.get({
110
+ userId: 'me',
111
+ id: draft.id,
112
+ format: 'metadata',
113
+ metadataHeaders: ['To', 'Subject', 'Date']
114
+ });
115
+ const data = draftRes.data;
116
+ const headers = extractHeaders(data.message?.payload?.headers);
117
+ return {
118
+ id: draft.id,
119
+ messageId: data.message?.id,
120
+ to: headers.to || '(no recipient)',
121
+ subject: headers.subject || '(no subject)',
122
+ snippet: data.message?.snippet || ''
123
+ };
124
+ }));
125
+ output(drafts, options);
126
+ return drafts;
127
+ }
128
+ /**
129
+ * Get a draft
130
+ */
131
+ export async function get(draftId, options = {}) {
132
+ const gmail = await getGmail();
133
+ const res = await gmail.users.drafts.get({
134
+ userId: 'me',
135
+ id: draftId,
136
+ format: 'full'
137
+ });
138
+ const headers = extractHeaders(res.data.message?.payload?.headers);
139
+ const body = extractBody(res.data.message?.payload);
140
+ const draft = {
141
+ id: res.data.id,
142
+ messageId: res.data.message?.id,
143
+ threadId: res.data.message?.threadId || undefined,
144
+ to: headers.to || '',
145
+ cc: headers.cc || null,
146
+ subject: headers.subject || '(no subject)',
147
+ body: body
148
+ };
149
+ output(draft, options);
150
+ return draft;
151
+ }
152
+ /**
153
+ * Create a new draft
154
+ */
155
+ export async function create(to, subject, body, options = {}) {
156
+ const gmail = await getGmail();
157
+ body = body.replace(/\\n/g, '\n');
158
+ let raw;
159
+ if (options.attach) {
160
+ const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
161
+ const attachmentPaths = attachments.map(a => ({ path: a }));
162
+ raw = createMultipartMessage({
163
+ to,
164
+ cc: options.cc,
165
+ bcc: options.bcc,
166
+ subject,
167
+ body,
168
+ attachments: attachmentPaths
169
+ });
170
+ }
171
+ else {
172
+ raw = createSimpleMessage({
173
+ to,
174
+ cc: options.cc,
175
+ bcc: options.bcc,
176
+ subject,
177
+ body
178
+ });
179
+ }
180
+ const res = await gmail.users.drafts.create({
181
+ userId: 'me',
182
+ requestBody: {
183
+ message: { raw }
184
+ }
185
+ });
186
+ const result = {
187
+ ok: true,
188
+ id: res.data.id || undefined,
189
+ messageId: res.data.message?.id,
190
+ to,
191
+ subject
192
+ };
193
+ output(result, options);
194
+ return result;
195
+ }
196
+ /**
197
+ * Create a draft reply to a message
198
+ */
199
+ export async function createReply(messageId, body, options = {}) {
200
+ const gmail = await getGmail();
201
+ // Get original message
202
+ const original = await gmail.users.messages.get({
203
+ userId: 'me',
204
+ id: messageId,
205
+ format: 'metadata',
206
+ metadataHeaders: ['From', 'To', 'Subject', 'Message-ID', 'References']
207
+ });
208
+ const headers = extractHeaders(original.data.payload?.headers);
209
+ // Determine reply-to address
210
+ const replyTo = headers.from;
211
+ let subject = headers.subject || '';
212
+ if (!subject.toLowerCase().startsWith('re:')) {
213
+ subject = `Re: ${subject}`;
214
+ }
215
+ body = body.replace(/\\n/g, '\n');
216
+ let raw;
217
+ if (options.attach) {
218
+ const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
219
+ const attachmentPaths = attachments.map(a => ({ path: a }));
220
+ raw = createMultipartMessage({
221
+ to: replyTo,
222
+ subject,
223
+ body,
224
+ inReplyTo: headers.message_id,
225
+ references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id,
226
+ attachments: attachmentPaths
227
+ });
228
+ }
229
+ else {
230
+ raw = createSimpleMessage({
231
+ to: replyTo,
232
+ subject,
233
+ body,
234
+ inReplyTo: headers.message_id,
235
+ references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id
236
+ });
237
+ }
238
+ const res = await gmail.users.drafts.create({
239
+ userId: 'me',
240
+ requestBody: {
241
+ message: {
242
+ raw,
243
+ threadId: original.data.threadId || undefined
244
+ }
245
+ }
246
+ });
247
+ const result = {
248
+ ok: true,
249
+ id: res.data.id || undefined,
250
+ messageId: res.data.message?.id,
251
+ threadId: original.data.threadId || undefined,
252
+ to: replyTo,
253
+ subject,
254
+ inReplyTo: messageId
255
+ };
256
+ output(result, options);
257
+ return result;
258
+ }
259
+ /**
260
+ * Update a draft
261
+ */
262
+ export async function update(draftId, to, subject, body, options = {}) {
263
+ const gmail = await getGmail();
264
+ body = body.replace(/\\n/g, '\n');
265
+ let raw;
266
+ if (options.attach) {
267
+ const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
268
+ const attachmentPaths = attachments.map(a => ({ path: a }));
269
+ raw = createMultipartMessage({
270
+ to,
271
+ cc: options.cc,
272
+ subject,
273
+ body,
274
+ attachments: attachmentPaths
275
+ });
276
+ }
277
+ else {
278
+ raw = createSimpleMessage({
279
+ to,
280
+ cc: options.cc,
281
+ subject,
282
+ body
283
+ });
284
+ }
285
+ const res = await gmail.users.drafts.update({
286
+ userId: 'me',
287
+ id: draftId,
288
+ requestBody: {
289
+ message: { raw }
290
+ }
291
+ });
292
+ const result = {
293
+ ok: true,
294
+ id: res.data.id || undefined,
295
+ messageId: res.data.message?.id,
296
+ to,
297
+ subject
298
+ };
299
+ output(result, options);
300
+ return result;
301
+ }
302
+ /**
303
+ * Delete a draft
304
+ */
305
+ export async function remove(draftId, options = {}) {
306
+ const gmail = await getGmail();
307
+ await gmail.users.drafts.delete({
308
+ userId: 'me',
309
+ id: draftId
310
+ });
311
+ const result = { ok: true, deleted: draftId };
312
+ output(result, options);
313
+ return result;
314
+ }
315
+ /**
316
+ * Send a draft
317
+ */
318
+ export async function send(draftId, options = {}) {
319
+ const gmail = await getGmail();
320
+ const res = await gmail.users.drafts.send({
321
+ userId: 'me',
322
+ requestBody: {
323
+ id: draftId
324
+ }
325
+ });
326
+ const result = {
327
+ ok: true,
328
+ id: res.data.id || undefined,
329
+ threadId: res.data.threadId || undefined,
330
+ sentDraft: draftId
331
+ };
332
+ output(result, options);
333
+ return result;
334
+ }
@@ -0,0 +1,47 @@
1
+ import { type OutputOptions } from './api.js';
2
+ /** Label data */
3
+ export interface LabelData {
4
+ id: string;
5
+ name: string;
6
+ type?: string;
7
+ messageListVisibility?: string;
8
+ labelListVisibility?: string;
9
+ messagesTotal?: number;
10
+ messagesUnread?: number;
11
+ threadsTotal?: number;
12
+ threadsUnread?: number;
13
+ }
14
+ /** Label action result */
15
+ export interface LabelActionResult {
16
+ ok: boolean;
17
+ id?: string;
18
+ name?: string;
19
+ messageId?: string;
20
+ labelAdded?: string;
21
+ labelRemoved?: string;
22
+ deleted?: string;
23
+ }
24
+ /**
25
+ * List all labels
26
+ */
27
+ export declare function list(options?: OutputOptions): Promise<LabelData[]>;
28
+ /**
29
+ * Get label details with counts
30
+ */
31
+ export declare function get(labelId: string, options?: OutputOptions): Promise<LabelData>;
32
+ /**
33
+ * Add label to message
34
+ */
35
+ export declare function addToMessage(messageId: string, labelName: string, options?: OutputOptions): Promise<LabelActionResult>;
36
+ /**
37
+ * Remove label from message
38
+ */
39
+ export declare function removeFromMessage(messageId: string, labelName: string, options?: OutputOptions): Promise<LabelActionResult>;
40
+ /**
41
+ * Create a new label
42
+ */
43
+ export declare function create(labelName: string, options?: OutputOptions): Promise<LabelActionResult>;
44
+ /**
45
+ * Delete a label
46
+ */
47
+ export declare function remove(labelName: string, options?: OutputOptions): Promise<LabelActionResult>;
@@ -0,0 +1,143 @@
1
+ import { getGmail, output } from './api.js';
2
+ /**
3
+ * List all labels
4
+ */
5
+ export async function list(options = {}) {
6
+ const gmail = await getGmail();
7
+ const res = await gmail.users.labels.list({
8
+ userId: 'me'
9
+ });
10
+ const labels = (res.data.labels || []).map(label => ({
11
+ id: label.id,
12
+ name: label.name,
13
+ type: label.type || undefined,
14
+ messageListVisibility: label.messageListVisibility || undefined,
15
+ labelListVisibility: label.labelListVisibility || undefined
16
+ }));
17
+ // Sort: system labels first, then user labels
18
+ labels.sort((a, b) => {
19
+ if (a.type === 'system' && b.type !== 'system')
20
+ return -1;
21
+ if (a.type !== 'system' && b.type === 'system')
22
+ return 1;
23
+ return a.name.localeCompare(b.name);
24
+ });
25
+ output(labels, options);
26
+ return labels;
27
+ }
28
+ /**
29
+ * Get label details with counts
30
+ */
31
+ export async function get(labelId, options = {}) {
32
+ const gmail = await getGmail();
33
+ // Resolve label name to ID if needed
34
+ const resolvedId = await resolveLabelId(gmail, labelId);
35
+ const res = await gmail.users.labels.get({
36
+ userId: 'me',
37
+ id: resolvedId
38
+ });
39
+ const label = {
40
+ id: res.data.id,
41
+ name: res.data.name,
42
+ type: res.data.type || undefined,
43
+ messagesTotal: res.data.messagesTotal || undefined,
44
+ messagesUnread: res.data.messagesUnread || undefined,
45
+ threadsTotal: res.data.threadsTotal || undefined,
46
+ threadsUnread: res.data.threadsUnread || undefined
47
+ };
48
+ output(label, options);
49
+ return label;
50
+ }
51
+ /**
52
+ * Add label to message
53
+ */
54
+ export async function addToMessage(messageId, labelName, options = {}) {
55
+ const gmail = await getGmail();
56
+ const labelId = await resolveLabelId(gmail, labelName);
57
+ await gmail.users.messages.modify({
58
+ userId: 'me',
59
+ id: messageId,
60
+ requestBody: {
61
+ addLabelIds: [labelId]
62
+ }
63
+ });
64
+ const result = { ok: true, messageId, labelAdded: labelName };
65
+ output(result, options);
66
+ return result;
67
+ }
68
+ /**
69
+ * Remove label from message
70
+ */
71
+ export async function removeFromMessage(messageId, labelName, options = {}) {
72
+ const gmail = await getGmail();
73
+ const labelId = await resolveLabelId(gmail, labelName);
74
+ await gmail.users.messages.modify({
75
+ userId: 'me',
76
+ id: messageId,
77
+ requestBody: {
78
+ removeLabelIds: [labelId]
79
+ }
80
+ });
81
+ const result = { ok: true, messageId, labelRemoved: labelName };
82
+ output(result, options);
83
+ return result;
84
+ }
85
+ /**
86
+ * Create a new label
87
+ */
88
+ export async function create(labelName, options = {}) {
89
+ const gmail = await getGmail();
90
+ const res = await gmail.users.labels.create({
91
+ userId: 'me',
92
+ requestBody: {
93
+ name: labelName,
94
+ labelListVisibility: 'labelShow',
95
+ messageListVisibility: 'show'
96
+ }
97
+ });
98
+ const result = {
99
+ ok: true,
100
+ id: res.data.id,
101
+ name: res.data.name
102
+ };
103
+ output(result, options);
104
+ return result;
105
+ }
106
+ /**
107
+ * Delete a label
108
+ */
109
+ export async function remove(labelName, options = {}) {
110
+ const gmail = await getGmail();
111
+ const labelId = await resolveLabelId(gmail, labelName);
112
+ // Don't allow deleting system labels
113
+ const systemLabels = ['INBOX', 'SENT', 'TRASH', 'SPAM', 'STARRED', 'UNREAD', 'IMPORTANT', 'DRAFT'];
114
+ if (labelId.startsWith('CATEGORY_') || systemLabels.includes(labelId)) {
115
+ throw new Error(`Cannot delete system label: ${labelName}`);
116
+ }
117
+ await gmail.users.labels.delete({
118
+ userId: 'me',
119
+ id: labelId
120
+ });
121
+ const result = { ok: true, deleted: labelName };
122
+ output(result, options);
123
+ return result;
124
+ }
125
+ /**
126
+ * Resolve label name to ID
127
+ */
128
+ async function resolveLabelId(gmail, labelNameOrId) {
129
+ // Check if already an ID (system labels or Label_xxx format)
130
+ const systemLabels = ['INBOX', 'SENT', 'TRASH', 'SPAM', 'STARRED', 'UNREAD', 'IMPORTANT', 'DRAFT', 'CHAT'];
131
+ if (labelNameOrId.startsWith('Label_') ||
132
+ labelNameOrId.startsWith('CATEGORY_') ||
133
+ systemLabels.includes(labelNameOrId.toUpperCase())) {
134
+ return labelNameOrId.toUpperCase();
135
+ }
136
+ // Look up by name
137
+ const res = await gmail.users.labels.list({ userId: 'me' });
138
+ const label = res.data.labels?.find(l => l.name?.toLowerCase() === labelNameOrId.toLowerCase());
139
+ if (!label) {
140
+ throw new Error(`Label not found: ${labelNameOrId}`);
141
+ }
142
+ return label.id;
143
+ }
@@ -0,0 +1,77 @@
1
+ import { type OutputOptions } from './api.js';
2
+ /** Message summary */
3
+ export interface MessageSummary {
4
+ id: string;
5
+ threadId?: string;
6
+ date: string | null;
7
+ from: string;
8
+ to: string;
9
+ subject: string;
10
+ snippet?: string;
11
+ labels?: string[];
12
+ unread?: boolean;
13
+ starred?: boolean;
14
+ important?: boolean;
15
+ body?: string;
16
+ cc?: string | null;
17
+ }
18
+ /** Action result */
19
+ export interface ActionResult {
20
+ ok: boolean;
21
+ archived?: string;
22
+ trashed?: string;
23
+ untrashed?: string;
24
+ starred?: string;
25
+ unstarred?: string;
26
+ markedRead?: string;
27
+ markedUnread?: string;
28
+ }
29
+ /** Message list options */
30
+ export interface MessageListOptions extends OutputOptions {
31
+ limit?: number;
32
+ body?: boolean;
33
+ }
34
+ /**
35
+ * List inbox messages
36
+ */
37
+ export declare function inbox(options?: MessageListOptions): Promise<MessageSummary[]>;
38
+ /**
39
+ * List unread messages
40
+ */
41
+ export declare function unread(options?: MessageListOptions): Promise<MessageSummary[]>;
42
+ /**
43
+ * Search messages with Gmail query syntax
44
+ */
45
+ export declare function search(query: string, options?: MessageListOptions): Promise<MessageSummary[]>;
46
+ /**
47
+ * Read a full message
48
+ */
49
+ export declare function read(messageId: string, options?: OutputOptions): Promise<MessageSummary>;
50
+ /**
51
+ * Archive a message (remove from INBOX)
52
+ */
53
+ export declare function archive(messageId: string, options?: OutputOptions): Promise<ActionResult>;
54
+ /**
55
+ * Trash a message
56
+ */
57
+ export declare function trash(messageId: string, options?: OutputOptions): Promise<ActionResult>;
58
+ /**
59
+ * Untrash a message
60
+ */
61
+ export declare function untrash(messageId: string, options?: OutputOptions): Promise<ActionResult>;
62
+ /**
63
+ * Star a message
64
+ */
65
+ export declare function star(messageId: string, options?: OutputOptions): Promise<ActionResult>;
66
+ /**
67
+ * Unstar a message
68
+ */
69
+ export declare function unstar(messageId: string, options?: OutputOptions): Promise<ActionResult>;
70
+ /**
71
+ * Mark as read
72
+ */
73
+ export declare function markRead(messageId: string, options?: OutputOptions): Promise<ActionResult>;
74
+ /**
75
+ * Mark as unread
76
+ */
77
+ export declare function markUnread(messageId: string, options?: OutputOptions): Promise<ActionResult>;