@cli4ai/gmail 1.0.11 → 1.0.14

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,223 @@
1
+ import { getGmail, output, formatDate, extractHeaders, extractBody, cleanBody } from './api.js';
2
+ /**
3
+ * List inbox messages
4
+ */
5
+ export async function inbox(options = {}) {
6
+ const gmail = await getGmail();
7
+ const limit = options.limit || 20;
8
+ const res = await gmail.users.messages.list({
9
+ userId: 'me',
10
+ maxResults: limit,
11
+ labelIds: ['INBOX']
12
+ });
13
+ if (!res.data.messages || res.data.messages.length === 0) {
14
+ output([], options);
15
+ return [];
16
+ }
17
+ const messages = await Promise.all(res.data.messages.map(msg => getMessageSummary(gmail, msg.id, options)));
18
+ output(messages, options);
19
+ return messages;
20
+ }
21
+ /**
22
+ * List unread messages
23
+ */
24
+ export async function unread(options = {}) {
25
+ const gmail = await getGmail();
26
+ const limit = options.limit || 20;
27
+ const res = await gmail.users.messages.list({
28
+ userId: 'me',
29
+ maxResults: limit,
30
+ q: 'is:unread'
31
+ });
32
+ if (!res.data.messages || res.data.messages.length === 0) {
33
+ output([], options);
34
+ return [];
35
+ }
36
+ const messages = await Promise.all(res.data.messages.map(msg => getMessageSummary(gmail, msg.id, options)));
37
+ output(messages, options);
38
+ return messages;
39
+ }
40
+ /**
41
+ * Search messages with Gmail query syntax
42
+ */
43
+ export async function search(query, options = {}) {
44
+ const gmail = await getGmail();
45
+ const limit = options.limit || 20;
46
+ const res = await gmail.users.messages.list({
47
+ userId: 'me',
48
+ maxResults: limit,
49
+ q: query
50
+ });
51
+ if (!res.data.messages || res.data.messages.length === 0) {
52
+ output([], options);
53
+ return [];
54
+ }
55
+ const messages = await Promise.all(res.data.messages.map(msg => getMessageSummary(gmail, msg.id, options)));
56
+ output(messages, options);
57
+ return messages;
58
+ }
59
+ /**
60
+ * Get message summary (for list views)
61
+ */
62
+ async function getMessageSummary(gmail, messageId, options = {}) {
63
+ const res = await gmail.users.messages.get({
64
+ userId: 'me',
65
+ id: messageId,
66
+ format: options.body ? 'full' : 'metadata',
67
+ metadataHeaders: ['From', 'To', 'Subject', 'Date']
68
+ });
69
+ const headers = extractHeaders(res.data.payload?.headers);
70
+ const labels = res.data.labelIds || [];
71
+ const summary = {
72
+ id: res.data.id,
73
+ threadId: res.data.threadId || undefined,
74
+ date: formatDate(res.data.internalDate),
75
+ from: headers.from || '',
76
+ to: headers.to || '',
77
+ subject: headers.subject || '(no subject)',
78
+ snippet: res.data.snippet || '',
79
+ labels: labels,
80
+ unread: labels.includes('UNREAD'),
81
+ starred: labels.includes('STARRED'),
82
+ important: labels.includes('IMPORTANT')
83
+ };
84
+ if (options.body) {
85
+ summary.body = cleanBody(extractBody(res.data.payload), 1000);
86
+ }
87
+ return summary;
88
+ }
89
+ /**
90
+ * Read a full message
91
+ */
92
+ export async function read(messageId, options = {}) {
93
+ const gmail = await getGmail();
94
+ const res = await gmail.users.messages.get({
95
+ userId: 'me',
96
+ id: messageId,
97
+ format: 'full'
98
+ });
99
+ const headers = extractHeaders(res.data.payload?.headers);
100
+ const labels = res.data.labelIds || [];
101
+ const body = extractBody(res.data.payload);
102
+ const message = {
103
+ id: res.data.id,
104
+ threadId: res.data.threadId || undefined,
105
+ date: formatDate(res.data.internalDate),
106
+ from: headers.from || '',
107
+ to: headers.to || '',
108
+ cc: headers.cc || null,
109
+ subject: headers.subject || '(no subject)',
110
+ body: body,
111
+ labels: labels,
112
+ unread: labels.includes('UNREAD'),
113
+ starred: labels.includes('STARRED')
114
+ };
115
+ output(message, options);
116
+ return message;
117
+ }
118
+ /**
119
+ * Archive a message (remove from INBOX)
120
+ */
121
+ export async function archive(messageId, options = {}) {
122
+ const gmail = await getGmail();
123
+ await gmail.users.messages.modify({
124
+ userId: 'me',
125
+ id: messageId,
126
+ requestBody: {
127
+ removeLabelIds: ['INBOX']
128
+ }
129
+ });
130
+ const result = { ok: true, archived: messageId };
131
+ output(result, options);
132
+ return result;
133
+ }
134
+ /**
135
+ * Trash a message
136
+ */
137
+ export async function trash(messageId, options = {}) {
138
+ const gmail = await getGmail();
139
+ await gmail.users.messages.trash({
140
+ userId: 'me',
141
+ id: messageId
142
+ });
143
+ const result = { ok: true, trashed: messageId };
144
+ output(result, options);
145
+ return result;
146
+ }
147
+ /**
148
+ * Untrash a message
149
+ */
150
+ export async function untrash(messageId, options = {}) {
151
+ const gmail = await getGmail();
152
+ await gmail.users.messages.untrash({
153
+ userId: 'me',
154
+ id: messageId
155
+ });
156
+ const result = { ok: true, untrashed: messageId };
157
+ output(result, options);
158
+ return result;
159
+ }
160
+ /**
161
+ * Star a message
162
+ */
163
+ export async function star(messageId, options = {}) {
164
+ const gmail = await getGmail();
165
+ await gmail.users.messages.modify({
166
+ userId: 'me',
167
+ id: messageId,
168
+ requestBody: {
169
+ addLabelIds: ['STARRED']
170
+ }
171
+ });
172
+ const result = { ok: true, starred: messageId };
173
+ output(result, options);
174
+ return result;
175
+ }
176
+ /**
177
+ * Unstar a message
178
+ */
179
+ export async function unstar(messageId, options = {}) {
180
+ const gmail = await getGmail();
181
+ await gmail.users.messages.modify({
182
+ userId: 'me',
183
+ id: messageId,
184
+ requestBody: {
185
+ removeLabelIds: ['STARRED']
186
+ }
187
+ });
188
+ const result = { ok: true, unstarred: messageId };
189
+ output(result, options);
190
+ return result;
191
+ }
192
+ /**
193
+ * Mark as read
194
+ */
195
+ export async function markRead(messageId, options = {}) {
196
+ const gmail = await getGmail();
197
+ await gmail.users.messages.modify({
198
+ userId: 'me',
199
+ id: messageId,
200
+ requestBody: {
201
+ removeLabelIds: ['UNREAD']
202
+ }
203
+ });
204
+ const result = { ok: true, markedRead: messageId };
205
+ output(result, options);
206
+ return result;
207
+ }
208
+ /**
209
+ * Mark as unread
210
+ */
211
+ export async function markUnread(messageId, options = {}) {
212
+ const gmail = await getGmail();
213
+ await gmail.users.messages.modify({
214
+ userId: 'me',
215
+ id: messageId,
216
+ requestBody: {
217
+ addLabelIds: ['UNREAD']
218
+ }
219
+ });
220
+ const result = { ok: true, markedUnread: messageId };
221
+ output(result, options);
222
+ return result;
223
+ }
@@ -0,0 +1,37 @@
1
+ import { type OutputOptions } from './api.js';
2
+ /** Send result */
3
+ export interface SendResult {
4
+ ok: boolean;
5
+ id?: string;
6
+ threadId?: string;
7
+ to?: string;
8
+ subject?: string;
9
+ inReplyTo?: string;
10
+ forwardedFrom?: string;
11
+ }
12
+ /** Send options */
13
+ export interface SendOptions extends OutputOptions {
14
+ cc?: string;
15
+ bcc?: string;
16
+ attach?: string | string[];
17
+ }
18
+ /**
19
+ * Send a new email
20
+ */
21
+ export declare function send(to: string, subject: string, body: string, options?: SendOptions): Promise<SendResult>;
22
+ /**
23
+ * Reply to a message
24
+ */
25
+ export declare function reply(messageId: string, body: string, options?: SendOptions): Promise<SendResult>;
26
+ /**
27
+ * Reply all
28
+ */
29
+ export declare function replyAll(messageId: string, body: string, options?: SendOptions): Promise<SendResult>;
30
+ /**
31
+ * Forward a message
32
+ */
33
+ export declare function forward(messageId: string, to: string, body?: string, options?: OutputOptions): Promise<SendResult>;
34
+ /**
35
+ * Create draft
36
+ */
37
+ export declare function draft(to: string, subject: string, body: string, options?: SendOptions): Promise<SendResult>;
@@ -0,0 +1,259 @@
1
+ import { getGmail, output, encodeBase64Url, extractHeaders, extractBody } from './api.js';
2
+ import { createMultipartMessage } from './drafts.js';
3
+ /**
4
+ * Create raw email message
5
+ */
6
+ function createRawMessage({ to, cc, bcc, subject, body, inReplyTo, references }) {
7
+ const lines = [];
8
+ lines.push(`To: ${to}`);
9
+ if (cc)
10
+ lines.push(`Cc: ${cc}`);
11
+ if (bcc)
12
+ lines.push(`Bcc: ${bcc}`);
13
+ lines.push(`Subject: ${subject}`);
14
+ lines.push('Content-Type: text/plain; charset=utf-8');
15
+ if (inReplyTo) {
16
+ lines.push(`In-Reply-To: ${inReplyTo}`);
17
+ lines.push(`References: ${references || inReplyTo}`);
18
+ }
19
+ lines.push('');
20
+ lines.push(body);
21
+ return encodeBase64Url(lines.join('\r\n'));
22
+ }
23
+ /**
24
+ * Send a new email
25
+ */
26
+ export async function send(to, subject, body, options = {}) {
27
+ const gmail = await getGmail();
28
+ // Process newlines in body
29
+ body = body.replace(/\\n/g, '\n');
30
+ let raw;
31
+ if (options.attach) {
32
+ const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
33
+ const attachmentPaths = attachments.map(a => ({ path: a }));
34
+ raw = createMultipartMessage({
35
+ to,
36
+ cc: options.cc,
37
+ bcc: options.bcc,
38
+ subject,
39
+ body,
40
+ attachments: attachmentPaths
41
+ });
42
+ }
43
+ else {
44
+ raw = createRawMessage({
45
+ to,
46
+ cc: options.cc,
47
+ bcc: options.bcc,
48
+ subject,
49
+ body
50
+ });
51
+ }
52
+ const res = await gmail.users.messages.send({
53
+ userId: 'me',
54
+ requestBody: { raw }
55
+ });
56
+ const result = {
57
+ ok: true,
58
+ id: res.data.id || undefined,
59
+ threadId: res.data.threadId || undefined,
60
+ to,
61
+ subject
62
+ };
63
+ output(result, options);
64
+ return result;
65
+ }
66
+ /**
67
+ * Reply to a message
68
+ */
69
+ export async function reply(messageId, body, options = {}) {
70
+ const gmail = await getGmail();
71
+ // Get original message to extract headers
72
+ const original = await gmail.users.messages.get({
73
+ userId: 'me',
74
+ id: messageId,
75
+ format: 'metadata',
76
+ metadataHeaders: ['From', 'To', 'Subject', 'Message-ID', 'References']
77
+ });
78
+ const headers = extractHeaders(original.data.payload?.headers);
79
+ // Determine who to reply to
80
+ const replyTo = headers.from;
81
+ let subject = headers.subject || '';
82
+ if (!subject.toLowerCase().startsWith('re:')) {
83
+ subject = `Re: ${subject}`;
84
+ }
85
+ // Process newlines in body
86
+ body = body.replace(/\\n/g, '\n');
87
+ let raw;
88
+ if (options.attach) {
89
+ const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
90
+ const attachmentPaths = attachments.map(a => ({ path: a }));
91
+ raw = createMultipartMessage({
92
+ to: replyTo,
93
+ subject,
94
+ body,
95
+ inReplyTo: headers.message_id,
96
+ references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id,
97
+ attachments: attachmentPaths
98
+ });
99
+ }
100
+ else {
101
+ raw = createRawMessage({
102
+ to: replyTo,
103
+ subject,
104
+ body,
105
+ inReplyTo: headers.message_id,
106
+ references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id
107
+ });
108
+ }
109
+ const res = await gmail.users.messages.send({
110
+ userId: 'me',
111
+ requestBody: {
112
+ raw,
113
+ threadId: original.data.threadId || undefined
114
+ }
115
+ });
116
+ const result = {
117
+ ok: true,
118
+ id: res.data.id || undefined,
119
+ threadId: res.data.threadId || undefined,
120
+ to: replyTo,
121
+ subject,
122
+ inReplyTo: messageId
123
+ };
124
+ output(result, options);
125
+ return result;
126
+ }
127
+ /**
128
+ * Reply all
129
+ */
130
+ export async function replyAll(messageId, body, options = {}) {
131
+ const gmail = await getGmail();
132
+ // Get original message
133
+ const original = await gmail.users.messages.get({
134
+ userId: 'me',
135
+ id: messageId,
136
+ format: 'metadata',
137
+ metadataHeaders: ['From', 'To', 'Cc', 'Subject', 'Message-ID', 'References']
138
+ });
139
+ const headers = extractHeaders(original.data.payload?.headers);
140
+ // Get my email address
141
+ const profile = await gmail.users.getProfile({ userId: 'me' });
142
+ const myEmail = profile.data.emailAddress;
143
+ // Build recipient list (original From + all To/Cc, excluding myself)
144
+ const allRecipients = new Set();
145
+ allRecipients.add(headers.from);
146
+ if (headers.to) {
147
+ headers.to.split(',').forEach(email => allRecipients.add(email.trim()));
148
+ }
149
+ if (headers.cc) {
150
+ headers.cc.split(',').forEach(email => allRecipients.add(email.trim()));
151
+ }
152
+ // Remove myself from recipients
153
+ const recipients = Array.from(allRecipients).filter(email => !email.toLowerCase().includes(myEmail.toLowerCase()));
154
+ let subject = headers.subject || '';
155
+ if (!subject.toLowerCase().startsWith('re:')) {
156
+ subject = `Re: ${subject}`;
157
+ }
158
+ body = body.replace(/\\n/g, '\n');
159
+ const raw = createRawMessage({
160
+ to: recipients.join(', '),
161
+ subject,
162
+ body,
163
+ inReplyTo: headers.message_id,
164
+ references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id
165
+ });
166
+ const res = await gmail.users.messages.send({
167
+ userId: 'me',
168
+ requestBody: {
169
+ raw,
170
+ threadId: original.data.threadId || undefined
171
+ }
172
+ });
173
+ const result = {
174
+ ok: true,
175
+ id: res.data.id || undefined,
176
+ threadId: res.data.threadId || undefined,
177
+ to: recipients.join(', '),
178
+ subject,
179
+ inReplyTo: messageId
180
+ };
181
+ output(result, options);
182
+ return result;
183
+ }
184
+ /**
185
+ * Forward a message
186
+ */
187
+ export async function forward(messageId, to, body, options = {}) {
188
+ const gmail = await getGmail();
189
+ // Get original message with full body
190
+ const original = await gmail.users.messages.get({
191
+ userId: 'me',
192
+ id: messageId,
193
+ format: 'full'
194
+ });
195
+ const headers = extractHeaders(original.data.payload?.headers);
196
+ const originalBody = extractBody(original.data.payload);
197
+ let subject = headers.subject || '';
198
+ if (!subject.toLowerCase().startsWith('fwd:')) {
199
+ subject = `Fwd: ${subject}`;
200
+ }
201
+ // Build forwarded message body
202
+ const forwardedContent = [
203
+ body ? body.replace(/\\n/g, '\n') : '',
204
+ '',
205
+ '---------- Forwarded message ----------',
206
+ `From: ${headers.from}`,
207
+ `Date: ${headers.date}`,
208
+ `Subject: ${headers.subject}`,
209
+ `To: ${headers.to}`,
210
+ '',
211
+ originalBody
212
+ ].join('\n');
213
+ const raw = createRawMessage({
214
+ to,
215
+ subject,
216
+ body: forwardedContent
217
+ });
218
+ const res = await gmail.users.messages.send({
219
+ userId: 'me',
220
+ requestBody: { raw }
221
+ });
222
+ const result = {
223
+ ok: true,
224
+ id: res.data.id || undefined,
225
+ threadId: res.data.threadId || undefined,
226
+ to,
227
+ subject,
228
+ forwardedFrom: messageId
229
+ };
230
+ output(result, options);
231
+ return result;
232
+ }
233
+ /**
234
+ * Create draft
235
+ */
236
+ export async function draft(to, subject, body, options = {}) {
237
+ const gmail = await getGmail();
238
+ body = body.replace(/\\n/g, '\n');
239
+ const raw = createRawMessage({
240
+ to,
241
+ cc: options.cc,
242
+ subject,
243
+ body
244
+ });
245
+ const res = await gmail.users.drafts.create({
246
+ userId: 'me',
247
+ requestBody: {
248
+ message: { raw }
249
+ }
250
+ });
251
+ const result = {
252
+ ok: true,
253
+ id: res.data.id || undefined,
254
+ to,
255
+ subject
256
+ };
257
+ output(result, options);
258
+ return result;
259
+ }
@@ -0,0 +1,55 @@
1
+ import { type OutputOptions } from './api.js';
2
+ /** Thread message */
3
+ export interface ThreadMessage {
4
+ id: string;
5
+ date: string | null;
6
+ from: string;
7
+ to: string;
8
+ subject: string;
9
+ body: string;
10
+ }
11
+ /** Thread data */
12
+ export interface ThreadData {
13
+ id: string;
14
+ subject: string;
15
+ messageCount: number;
16
+ messages: ThreadMessage[];
17
+ }
18
+ /** Thread summary */
19
+ export interface ThreadSummary {
20
+ id: string;
21
+ subject: string;
22
+ from: string;
23
+ lastFrom: string;
24
+ date: string | null;
25
+ messageCount: number;
26
+ snippet: string;
27
+ }
28
+ /** Thread action result */
29
+ export interface ThreadActionResult {
30
+ ok: boolean;
31
+ archived?: string;
32
+ trashed?: string;
33
+ }
34
+ /** Thread options */
35
+ export interface ThreadOptions extends OutputOptions {
36
+ fullBody?: boolean;
37
+ limit?: number;
38
+ query?: string;
39
+ }
40
+ /**
41
+ * Get full thread (conversation)
42
+ */
43
+ export declare function get(threadId: string, options?: ThreadOptions): Promise<ThreadData>;
44
+ /**
45
+ * List recent threads
46
+ */
47
+ export declare function list(options?: ThreadOptions): Promise<ThreadSummary[]>;
48
+ /**
49
+ * Archive entire thread
50
+ */
51
+ export declare function archive(threadId: string, options?: OutputOptions): Promise<ThreadActionResult>;
52
+ /**
53
+ * Trash entire thread
54
+ */
55
+ export declare function trash(threadId: string, options?: OutputOptions): Promise<ThreadActionResult>;
@@ -0,0 +1,101 @@
1
+ import { getGmail, output, formatDate, extractHeaders, extractBody, cleanBody } from './api.js';
2
+ /**
3
+ * Get full thread (conversation)
4
+ */
5
+ export async function get(threadId, options = {}) {
6
+ const gmail = await getGmail();
7
+ const res = await gmail.users.threads.get({
8
+ userId: 'me',
9
+ id: threadId,
10
+ format: 'full'
11
+ });
12
+ const messages = (res.data.messages || []).map(msg => {
13
+ const headers = extractHeaders(msg.payload?.headers);
14
+ return {
15
+ id: msg.id,
16
+ date: formatDate(msg.internalDate),
17
+ from: headers.from || '',
18
+ to: headers.to || '',
19
+ subject: headers.subject || '(no subject)',
20
+ body: cleanBody(extractBody(msg.payload), options.fullBody ? null : 2000)
21
+ };
22
+ });
23
+ const thread = {
24
+ id: res.data.id,
25
+ subject: messages[0]?.subject || '(no subject)',
26
+ messageCount: messages.length,
27
+ messages: messages
28
+ };
29
+ output(thread, options);
30
+ return thread;
31
+ }
32
+ /**
33
+ * List recent threads
34
+ */
35
+ export async function list(options = {}) {
36
+ const gmail = await getGmail();
37
+ const limit = options.limit || 20;
38
+ const query = options.query || '';
39
+ const res = await gmail.users.threads.list({
40
+ userId: 'me',
41
+ maxResults: limit,
42
+ q: query
43
+ });
44
+ if (!res.data.threads || res.data.threads.length === 0) {
45
+ output([], options);
46
+ return [];
47
+ }
48
+ // Get summary of each thread
49
+ const threads = await Promise.all(res.data.threads.map(async (thread) => {
50
+ const threadData = await gmail.users.threads.get({
51
+ userId: 'me',
52
+ id: thread.id,
53
+ format: 'metadata',
54
+ metadataHeaders: ['From', 'Subject', 'Date']
55
+ });
56
+ const firstMsg = threadData.data.messages?.[0];
57
+ const lastMsg = threadData.data.messages?.[threadData.data.messages.length - 1];
58
+ const firstHeaders = extractHeaders(firstMsg?.payload?.headers);
59
+ const lastHeaders = extractHeaders(lastMsg?.payload?.headers);
60
+ return {
61
+ id: thread.id,
62
+ subject: firstHeaders.subject || '(no subject)',
63
+ from: firstHeaders.from || '',
64
+ lastFrom: lastHeaders.from || '',
65
+ date: formatDate(lastMsg?.internalDate),
66
+ messageCount: threadData.data.messages?.length || 0,
67
+ snippet: thread.snippet || ''
68
+ };
69
+ }));
70
+ output(threads, options);
71
+ return threads;
72
+ }
73
+ /**
74
+ * Archive entire thread
75
+ */
76
+ export async function archive(threadId, options = {}) {
77
+ const gmail = await getGmail();
78
+ await gmail.users.threads.modify({
79
+ userId: 'me',
80
+ id: threadId,
81
+ requestBody: {
82
+ removeLabelIds: ['INBOX']
83
+ }
84
+ });
85
+ const result = { ok: true, archived: threadId };
86
+ output(result, options);
87
+ return result;
88
+ }
89
+ /**
90
+ * Trash entire thread
91
+ */
92
+ export async function trash(threadId, options = {}) {
93
+ const gmail = await getGmail();
94
+ await gmail.users.threads.trash({
95
+ userId: 'me',
96
+ id: threadId
97
+ });
98
+ const result = { ok: true, trashed: threadId };
99
+ output(result, options);
100
+ return result;
101
+ }
package/dist/run.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};