dooray-mail-cli 0.1.2 ā 0.2.0
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/README.md +101 -8
- package/SKILL.md +135 -4
- package/dist/mail-client.js +107 -8
- package/dooray-cli.js +399 -14
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,19 +1,112 @@
|
|
|
1
|
-
# Dooray
|
|
1
|
+
# Dooray Mail CLI
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/dooray-mail-cli)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
2
5
|
|
|
3
6
|
Dooray mail CLI for OpenClaw - IMAP/SMTP integration
|
|
4
7
|
|
|
5
|
-
## Installation
|
|
8
|
+
## š Installation
|
|
6
9
|
|
|
7
10
|
```bash
|
|
8
|
-
npm install -g
|
|
11
|
+
npm install -g dooray-mail-cli
|
|
9
12
|
```
|
|
10
13
|
|
|
11
|
-
## Usage
|
|
14
|
+
## š Usage
|
|
12
15
|
|
|
16
|
+
### Setup
|
|
13
17
|
```bash
|
|
14
|
-
dooray-cli config #
|
|
15
|
-
dooray-cli recent # List recent mails
|
|
16
|
-
dooray-cli test # Test connection
|
|
18
|
+
dooray-cli config # Configure email account
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
### Check Emails
|
|
22
|
+
```bash
|
|
23
|
+
dooray-cli recent # List 10 recent mails
|
|
24
|
+
dooray-cli list # List unread mails only
|
|
25
|
+
dooray-cli read 123 # Read specific mail by UID
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Send & Reply
|
|
29
|
+
```bash
|
|
30
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --body "Message"
|
|
31
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --body "Message" --cc "cc@example.com" --attach "./file.pdf"
|
|
32
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --html "<h1>Title</h1>"
|
|
33
|
+
dooray-cli reply 123 --body "Thank you!" # Reply to mail UID 123
|
|
34
|
+
dooray-cli forward 123 --to "another@example.com" --body "FYI"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Manage Emails
|
|
38
|
+
```bash
|
|
39
|
+
dooray-cli delete 123 # Move to trash
|
|
40
|
+
dooray-cli delete 123 --force # Permanently delete
|
|
41
|
+
dooray-cli mark 123 --read # Mark as read
|
|
42
|
+
dooray-cli mark 123 --unread # Mark as unread
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Attachments
|
|
46
|
+
```bash
|
|
47
|
+
dooray-cli attachments 123 # List attachments
|
|
48
|
+
dooray-cli download 123 # Download all
|
|
49
|
+
dooray-cli download 123 --file 1 # Download specific file
|
|
50
|
+
dooray-cli download 123 --output ./my-files # Custom output path
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Search with Filters
|
|
54
|
+
```bash
|
|
55
|
+
dooray-cli search "keyword" # Search by keyword
|
|
56
|
+
dooray-cli search "meeting" --from "boss@example.com" # Filter by sender
|
|
57
|
+
dooray-cli search "report" --since "2026-01-01" # Filter by date
|
|
58
|
+
dooray-cli search "invoice" --before "2026-02-01" # Before date
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Other Commands
|
|
62
|
+
```bash
|
|
63
|
+
dooray-cli unread # Show unread count
|
|
64
|
+
dooray-cli test # Test IMAP/SMTP connection
|
|
65
|
+
dooray-cli help # Show all commands
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## š§ Features
|
|
69
|
+
|
|
70
|
+
- ā
IMAP/SMTP mail operations
|
|
71
|
+
- ā
Read/Send/Reply/Forward emails
|
|
72
|
+
- ā
**Email threading support** (Reply with In-Reply-To headers)
|
|
73
|
+
- ā
**CC/BCC support** (v0.2.0+)
|
|
74
|
+
- ā
**HTML mail support** (v0.2.0+)
|
|
75
|
+
- ā
**Attachments management** (List & Download) (v0.1.3+)
|
|
76
|
+
- ā
**Send with attachments** (v0.2.0+)
|
|
77
|
+
- ā
**Delete & Mark as read/unread** (v0.2.0+)
|
|
78
|
+
- ā
**Advanced search filters** (Date, Sender) (v0.2.0+)
|
|
79
|
+
- ā
AES-256 password encryption
|
|
80
|
+
- ā
Cross-platform (Windows, macOS, Linux)
|
|
81
|
+
- ā
OpenClaw Skill integration
|
|
82
|
+
|
|
83
|
+
## š Requirements
|
|
84
|
+
|
|
85
|
+
- Node.js 14+
|
|
86
|
+
- Dooray email account with IMAP/SMTP access
|
|
87
|
+
|
|
88
|
+
**Default Dooray Settings:**
|
|
89
|
+
- IMAP: `imap.dooray.com:993` (SSL)
|
|
90
|
+
- SMTP: `smtp.dooray.com:465` (SSL)
|
|
91
|
+
|
|
92
|
+
## š Security
|
|
93
|
+
|
|
94
|
+
- Passwords are encrypted with AES-256
|
|
95
|
+
- Configuration stored locally: `~/.dooray-config.json`
|
|
96
|
+
- Mail cache for replies: `~/.dooray-mail-cache.json`
|
|
97
|
+
|
|
98
|
+
## š Documentation
|
|
99
|
+
|
|
100
|
+
See [SKILL.md](./SKILL.md) for detailed command reference and OpenClaw integration guide.
|
|
101
|
+
|
|
102
|
+
## š License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
105
|
+
|
|
106
|
+
## š¤ Contributing
|
|
107
|
+
|
|
108
|
+
Issues and pull requests are welcome!
|
|
109
|
+
|
|
110
|
+
## š¦ Package
|
|
111
|
+
|
|
112
|
+
- npm: https://www.npmjs.com/package/dooray-mail-cli
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dooray
|
|
3
|
-
description: "
|
|
3
|
+
description: "Complete Dooray email management via IMAP/SMTP using dooray-cli. Features: List/Read/Send/Reply/Forward emails, CC/BCC support, HTML mail, attachments (download & send), delete, mark read/unread, advanced search (date, sender filters). Use for any Dooray mail operations."
|
|
4
4
|
homepage: https://dooray.com
|
|
5
5
|
metadata:
|
|
6
6
|
{
|
|
@@ -146,13 +146,144 @@ dooray-cli reply 123
|
|
|
146
146
|
- Include proper email threading headers (In-Reply-To, References)
|
|
147
147
|
- Appear in the same conversation thread in email clients
|
|
148
148
|
|
|
149
|
-
###
|
|
149
|
+
### Forward an Email
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
Forward an existing email to another recipient:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
dooray-cli forward 123 --to "colleague@example.com" --body "Please see below"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Features:
|
|
158
|
+
- Automatically includes original email content
|
|
159
|
+
- Preserves original attachments
|
|
160
|
+
- Adds "Fwd:" prefix to subject (if not already present)
|
|
161
|
+
|
|
162
|
+
### Delete Emails
|
|
163
|
+
|
|
164
|
+
**Move to trash:**
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
dooray-cli delete 123
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Permanently delete:**
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
dooray-cli delete 123 --force
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Mark as Read/Unread
|
|
177
|
+
|
|
178
|
+
**Mark as read:**
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
dooray-cli mark 123 --read
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Mark as unread:**
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
dooray-cli mark 123 --unread
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Send with Advanced Options
|
|
191
|
+
|
|
192
|
+
**Send with CC/BCC:**
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --body "Message" --cc "cc@example.com" --bcc "bcc@example.com"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Send HTML email:**
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
dooray-cli send --to "user@example.com" --subject "Newsletter" --html "<h1>Title</h1><p>Content</p>"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Send with attachments:**
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
dooray-cli send --to "user@example.com" --subject "Report" --body "Please find attached" --attach "./report.pdf" --attach "./data.xlsx"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Combine all options:**
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
dooray-cli send --to "user@example.com" --subject "Q4 Report" --body "Please review" --cc "boss@example.com" --attach "./report.pdf"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Manage Attachments
|
|
217
|
+
|
|
218
|
+
**List attachments in an email:**
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
dooray-cli attachments 123
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Output:
|
|
225
|
+
```
|
|
226
|
+
š Attachments (3)
|
|
227
|
+
|
|
228
|
+
1. document.pdf
|
|
229
|
+
Type: application/pdf
|
|
230
|
+
Size: 245.50 KB
|
|
231
|
+
|
|
232
|
+
2. image.png
|
|
233
|
+
Type: image/png
|
|
234
|
+
Size: 128.34 KB
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Download all attachments:**
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
dooray-cli download 123
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Files are saved to: `./downloads/mail-123/`
|
|
244
|
+
|
|
245
|
+
**Download specific attachment:**
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
dooray-cli download 123 --file 1
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Downloads only the first attachment.
|
|
252
|
+
|
|
253
|
+
**Custom download path:**
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
dooray-cli download 123 --output ./my-files
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Files are saved to: `./my-files/mail-123/`
|
|
260
|
+
|
|
261
|
+
### Search Emails with Filters
|
|
262
|
+
|
|
263
|
+
**Search by keywords:**
|
|
152
264
|
|
|
153
265
|
```bash
|
|
154
266
|
dooray-cli search "meeting"
|
|
155
|
-
dooray-cli search "project
|
|
267
|
+
dooray-cli search "project update"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Filter by sender:**
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
dooray-cli search "report" --from "boss@example.com"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Filter by date range:**
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
dooray-cli search "invoice" --since "2026-01-01"
|
|
280
|
+
dooray-cli search "receipt" --before "2026-02-01"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Combine filters:**
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
dooray-cli search "meeting" --from "boss@example.com" --since "2026-02-01"
|
|
156
287
|
```
|
|
157
288
|
|
|
158
289
|
### Check Unread Count
|
package/dist/mail-client.js
CHANGED
|
@@ -218,6 +218,65 @@ class DoorayMailClient {
|
|
|
218
218
|
return null;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* ė©ģ¼ ģģ (ķ“ģ§ķµģ¼ė” ģ“ė ėė ģźµ¬ ģģ )
|
|
223
|
+
*/
|
|
224
|
+
async deleteMail(uid, permanent = false) {
|
|
225
|
+
let connection;
|
|
226
|
+
try {
|
|
227
|
+
connection = await this.getImapConnection();
|
|
228
|
+
await connection.openBox('INBOX');
|
|
229
|
+
|
|
230
|
+
if (permanent) {
|
|
231
|
+
// ģźµ¬ ģģ
|
|
232
|
+
await connection.addFlags([uid], ['\\Deleted']);
|
|
233
|
+
await connection.imap.expunge();
|
|
234
|
+
console.log('[Dooray] Mail permanently deleted:', uid);
|
|
235
|
+
} else {
|
|
236
|
+
// ķ“ģ§ķµģ¼ė” ģ“ė (\\Deleted ķėź·øė§ ģ¶ź°)
|
|
237
|
+
await connection.addFlags([uid], ['\\Deleted']);
|
|
238
|
+
console.log('[Dooray] Mail moved to trash:', uid);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
connection.end();
|
|
242
|
+
return { success: true };
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
const err = error;
|
|
246
|
+
console.error('[Dooray] Failed to delete mail:', err.message);
|
|
247
|
+
if (connection)
|
|
248
|
+
connection.end();
|
|
249
|
+
return { success: false };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* ė©ģ¼ ģ½ģ/ģģ½ģ ķģ ė³ź²½
|
|
254
|
+
*/
|
|
255
|
+
async markMail(uid, asRead) {
|
|
256
|
+
let connection;
|
|
257
|
+
try {
|
|
258
|
+
connection = await this.getImapConnection();
|
|
259
|
+
await connection.openBox('INBOX');
|
|
260
|
+
|
|
261
|
+
if (asRead) {
|
|
262
|
+
await connection.addFlags([uid], ['\\Seen']);
|
|
263
|
+
console.log('[Dooray] Mail marked as read:', uid);
|
|
264
|
+
} else {
|
|
265
|
+
await connection.delFlags([uid], ['\\Seen']);
|
|
266
|
+
console.log('[Dooray] Mail marked as unread:', uid);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
connection.end();
|
|
270
|
+
return { success: true };
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
const err = error;
|
|
274
|
+
console.error('[Dooray] Failed to mark mail:', err.message);
|
|
275
|
+
if (connection)
|
|
276
|
+
connection.end();
|
|
277
|
+
return { success: false };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
221
280
|
/**
|
|
222
281
|
* ė©ģ¼ ė°ģ”
|
|
223
282
|
*/
|
|
@@ -271,24 +330,58 @@ class DoorayMailClient {
|
|
|
271
330
|
}
|
|
272
331
|
}
|
|
273
332
|
/**
|
|
274
|
-
* ė©ģ¼ ź²ģ
|
|
333
|
+
* ė©ģ¼ ź²ģ (ķ¤ģė, ė ģ§, ė°ģ ģ ķķ° ģ§ģ)
|
|
275
334
|
*/
|
|
276
|
-
async searchMail(
|
|
335
|
+
async searchMail(keywords, options = {}, limit = 10) {
|
|
277
336
|
let connection;
|
|
278
337
|
try {
|
|
279
338
|
connection = await this.getImapConnection();
|
|
280
339
|
await connection.openBox('INBOX');
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
340
|
+
|
|
341
|
+
// ź²ģ 씰걓 ģģ±
|
|
342
|
+
const searchParts = [];
|
|
343
|
+
|
|
344
|
+
// ķ¤ģė ź²ģ (ģ ėŖ© ėė 본문)
|
|
345
|
+
if (keywords && keywords.length > 0) {
|
|
346
|
+
const keywordStr = Array.isArray(keywords) ? keywords.join(' ') : keywords;
|
|
347
|
+
if (keywordStr.trim()) {
|
|
348
|
+
searchParts.push(['OR', ['SUBJECT', keywordStr], ['BODY', keywordStr]]);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ė°ģ ģ ķķ°
|
|
353
|
+
if (options.from) {
|
|
354
|
+
searchParts.push(['FROM', options.from]);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ė ģ§ ķķ° (since)
|
|
358
|
+
if (options.since) {
|
|
359
|
+
const sinceDate = new Date(options.since);
|
|
360
|
+
searchParts.push(['SINCE', sinceDate]);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ė ģ§ ķķ° (before)
|
|
364
|
+
if (options.before) {
|
|
365
|
+
const beforeDate = new Date(options.before);
|
|
366
|
+
searchParts.push(['BEFORE', beforeDate]);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ź²ģ ģ”°ź±“ģ“ ģģ¼ė©“ ėŖØė ė©ģ¼ ė°ķ
|
|
370
|
+
let searchCriteria;
|
|
371
|
+
if (searchParts.length === 0) {
|
|
372
|
+
searchCriteria = ['ALL'];
|
|
373
|
+
} else if (searchParts.length === 1) {
|
|
374
|
+
searchCriteria = searchParts[0];
|
|
375
|
+
} else {
|
|
376
|
+
searchCriteria = ['AND', ...searchParts];
|
|
377
|
+
}
|
|
378
|
+
|
|
287
379
|
const fetchOptions = {
|
|
288
380
|
bodies: ['HEADER', 'TEXT', ''],
|
|
289
381
|
struct: true
|
|
290
382
|
};
|
|
291
383
|
const messages = await connection.search(searchCriteria, fetchOptions);
|
|
384
|
+
|
|
292
385
|
// ģµģ ė©ģ¼ė¶ķ° limitź°ė§
|
|
293
386
|
const limitedMessages = messages.slice(0, limit);
|
|
294
387
|
const parsedMails = [];
|
|
@@ -337,6 +430,12 @@ class DoorayMailClient {
|
|
|
337
430
|
receivedAt: parsed.date?.toISOString() || new Date().toISOString(),
|
|
338
431
|
isRead: flags.includes('\\Seen'),
|
|
339
432
|
hasAttachments: (parsed.attachments?.length || 0) > 0,
|
|
433
|
+
attachments: (parsed.attachments || []).map((att) => ({
|
|
434
|
+
filename: att.filename || 'unnamed',
|
|
435
|
+
contentType: att.contentType || 'application/octet-stream',
|
|
436
|
+
size: att.size || 0,
|
|
437
|
+
content: att.content
|
|
438
|
+
})),
|
|
340
439
|
inReplyTo: parsed.inReplyTo || '',
|
|
341
440
|
references: parsed.references || []
|
|
342
441
|
};
|
package/dooray-cli.js
CHANGED
|
@@ -83,6 +83,21 @@ async function handleCommand() {
|
|
|
83
83
|
case 'reply':
|
|
84
84
|
await replyCommand(args[1], args.slice(2));
|
|
85
85
|
break;
|
|
86
|
+
case 'forward':
|
|
87
|
+
await forwardCommand(args[1], args.slice(2));
|
|
88
|
+
break;
|
|
89
|
+
case 'delete':
|
|
90
|
+
await deleteCommand(args[1], args.slice(2));
|
|
91
|
+
break;
|
|
92
|
+
case 'mark':
|
|
93
|
+
await markCommand(args[1], args.slice(2));
|
|
94
|
+
break;
|
|
95
|
+
case 'attachments':
|
|
96
|
+
await attachmentsCommand(args[1]);
|
|
97
|
+
break;
|
|
98
|
+
case 'download':
|
|
99
|
+
await downloadCommand(args[1], args.slice(2));
|
|
100
|
+
break;
|
|
86
101
|
case 'search':
|
|
87
102
|
await searchCommand(args.slice(1));
|
|
88
103
|
break;
|
|
@@ -227,7 +242,17 @@ async function readCommand(uid) {
|
|
|
227
242
|
console.log(`Subject: ${mail.subject}`);
|
|
228
243
|
console.log(`Date: ${mail.receivedAt}`);
|
|
229
244
|
console.log(`Read: ${mail.isRead ? 'Yes' : 'No'}`);
|
|
230
|
-
|
|
245
|
+
|
|
246
|
+
if (mail.attachments && mail.attachments.length > 0) {
|
|
247
|
+
console.log(`Attachments: ${mail.attachments.length} file(s)`);
|
|
248
|
+
mail.attachments.forEach((att, idx) => {
|
|
249
|
+
const sizeKB = (att.size / 1024).toFixed(2);
|
|
250
|
+
console.log(` ${idx + 1}. ${att.filename} (${sizeKB} KB)`);
|
|
251
|
+
});
|
|
252
|
+
console.log();
|
|
253
|
+
} else {
|
|
254
|
+
console.log(`Attachments: No\n`);
|
|
255
|
+
}
|
|
231
256
|
console.log('ā'.repeat(50));
|
|
232
257
|
console.log(mail.body || mail.bodyHtml || '(No content)');
|
|
233
258
|
console.log('ā'.repeat(50));
|
|
@@ -314,10 +339,268 @@ async function replyCommand(uid, args) {
|
|
|
314
339
|
}
|
|
315
340
|
}
|
|
316
341
|
|
|
342
|
+
// forward: ė©ģ¼ ģ ė¬
|
|
343
|
+
async function forwardCommand(uid, args) {
|
|
344
|
+
if (!uid) {
|
|
345
|
+
console.error('ā Usage: dooray-cli forward <uid> --to <email> [--body "message"]');
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// --to, --body ģµģ
ķģ±
|
|
350
|
+
let to, body;
|
|
351
|
+
for (let i = 0; i < args.length; i++) {
|
|
352
|
+
if (args[i] === '--to' && args[i + 1]) {
|
|
353
|
+
to = args[++i];
|
|
354
|
+
} else if (args[i] === '--body' && args[i + 1]) {
|
|
355
|
+
body = args[++i];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!to) {
|
|
360
|
+
console.error('ā --to option is required');
|
|
361
|
+
console.error('Usage: dooray-cli forward <uid> --to <email> [--body "message"]');
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const config = loadConfig();
|
|
366
|
+
const client = await createClient(config);
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const mail = await client.getMailById(uid);
|
|
370
|
+
|
|
371
|
+
if (!mail) {
|
|
372
|
+
console.error('ā Mail not found');
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ģ ė¬ ģ ėŖ© ģģ±
|
|
377
|
+
const fwdSubject = mail.subject.startsWith('Fwd:') || mail.subject.startsWith('FW:')
|
|
378
|
+
? mail.subject
|
|
379
|
+
: `Fwd: ${mail.subject}`;
|
|
380
|
+
|
|
381
|
+
// ģ ė¬ ė³øė¬ø ģģ± (ģė³ø ė©ģ¼ ķ¬ķØ)
|
|
382
|
+
const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
|
|
383
|
+
const originalMessage = `
|
|
384
|
+
---------- Forwarded message ---------
|
|
385
|
+
From: ${fromText}
|
|
386
|
+
Date: ${mail.receivedAt}
|
|
387
|
+
Subject: ${mail.subject}
|
|
388
|
+
|
|
389
|
+
${mail.body || mail.bodyHtml || ''}
|
|
390
|
+
`;
|
|
391
|
+
|
|
392
|
+
const finalBody = body ? `${body}\n\n${originalMessage}` : originalMessage;
|
|
393
|
+
|
|
394
|
+
// ė©ģ¼ ģ ģ” (첨ė¶ķģ¼ ķ¬ķØ)
|
|
395
|
+
await client.sendMail({
|
|
396
|
+
to: to,
|
|
397
|
+
subject: fwdSubject,
|
|
398
|
+
text: finalBody,
|
|
399
|
+
attachments: mail.attachments && mail.attachments.length > 0
|
|
400
|
+
? mail.attachments.map(att => ({
|
|
401
|
+
filename: att.filename,
|
|
402
|
+
content: att.content,
|
|
403
|
+
contentType: att.contentType
|
|
404
|
+
}))
|
|
405
|
+
: undefined
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
console.log('\nā
Mail forwarded successfully');
|
|
409
|
+
console.log(` To: ${to}`);
|
|
410
|
+
console.log(` Subject: ${fwdSubject}`);
|
|
411
|
+
if (mail.attachments && mail.attachments.length > 0) {
|
|
412
|
+
console.log(` Attachments: ${mail.attachments.length} file(s)`);
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error('ā Error:', error.message);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
} finally {
|
|
418
|
+
await client.close();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// delete: ė©ģ¼ ģģ
|
|
423
|
+
async function deleteCommand(uid, args) {
|
|
424
|
+
if (!uid) {
|
|
425
|
+
console.error('ā Usage: dooray-cli delete <uid> [--force]');
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const permanent = args.includes('--force');
|
|
430
|
+
|
|
431
|
+
const config = loadConfig();
|
|
432
|
+
const client = await createClient(config);
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const result = await client.deleteMail(uid, permanent);
|
|
436
|
+
|
|
437
|
+
if (result.success) {
|
|
438
|
+
if (permanent) {
|
|
439
|
+
console.log('ā
Mail permanently deleted');
|
|
440
|
+
} else {
|
|
441
|
+
console.log('ā
Mail moved to trash');
|
|
442
|
+
console.log('š” Use --force to permanently delete');
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
console.error('ā Failed to delete mail');
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
} finally {
|
|
449
|
+
await client.close();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// mark: ģ½ģ/ģģ½ģ ķģ
|
|
454
|
+
async function markCommand(uid, args) {
|
|
455
|
+
if (!uid) {
|
|
456
|
+
console.error('ā Usage: dooray-cli mark <uid> --read|--unread');
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const asRead = args.includes('--read');
|
|
461
|
+
const asUnread = args.includes('--unread');
|
|
462
|
+
|
|
463
|
+
if (!asRead && !asUnread) {
|
|
464
|
+
console.error('ā Specify --read or --unread');
|
|
465
|
+
process.exit(1);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const config = loadConfig();
|
|
469
|
+
const client = await createClient(config);
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const result = await client.markMail(uid, asRead);
|
|
473
|
+
|
|
474
|
+
if (result.success) {
|
|
475
|
+
console.log(`ā
Mail marked as ${asRead ? 'read' : 'unread'}`);
|
|
476
|
+
} else {
|
|
477
|
+
console.error('ā Failed to mark mail');
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
await client.close();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// attachments: 첨ė¶ķģ¼ ėŖ©ė” ė³“źø°
|
|
486
|
+
async function attachmentsCommand(uid) {
|
|
487
|
+
if (!uid) {
|
|
488
|
+
console.error('ā Usage: dooray-cli attachments <uid>');
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const config = loadConfig();
|
|
493
|
+
const client = await createClient(config);
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const mail = await client.getMailById(uid);
|
|
497
|
+
|
|
498
|
+
if (!mail) {
|
|
499
|
+
console.error('ā Mail not found');
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!mail.attachments || mail.attachments.length === 0) {
|
|
504
|
+
console.log('š No attachments in this mail');
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.log(`\nš Attachments (${mail.attachments.length})\n`);
|
|
509
|
+
mail.attachments.forEach((att, idx) => {
|
|
510
|
+
const sizeKB = (att.size / 1024).toFixed(2);
|
|
511
|
+
console.log(`${idx + 1}. ${att.filename}`);
|
|
512
|
+
console.log(` Type: ${att.contentType}`);
|
|
513
|
+
console.log(` Size: ${sizeKB} KB\n`);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
console.log('š” To download: dooray-cli download ' + uid);
|
|
517
|
+
} finally {
|
|
518
|
+
await client.close();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// download: 첨ė¶ķģ¼ ė¤ģ“ė”ė
|
|
523
|
+
async function downloadCommand(uid, args) {
|
|
524
|
+
if (!uid) {
|
|
525
|
+
console.error('ā Usage: dooray-cli download <uid> [--output <path>] [--file <index>]');
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ģµģ
ķģ±
|
|
530
|
+
let outputPath = './downloads';
|
|
531
|
+
let fileIndex = null;
|
|
532
|
+
|
|
533
|
+
for (let i = 0; i < args.length; i++) {
|
|
534
|
+
if (args[i] === '--output' && args[i + 1]) {
|
|
535
|
+
outputPath = args[++i];
|
|
536
|
+
} else if (args[i] === '--file' && args[i + 1]) {
|
|
537
|
+
fileIndex = parseInt(args[++i]) - 1;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const config = loadConfig();
|
|
542
|
+
const client = await createClient(config);
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
const mail = await client.getMailById(uid);
|
|
546
|
+
|
|
547
|
+
if (!mail) {
|
|
548
|
+
console.error('ā Mail not found');
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (!mail.attachments || mail.attachments.length === 0) {
|
|
553
|
+
console.log('š No attachments to download');
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ė¤ģ“ė”ė ėė ķ 리 ģģ±
|
|
558
|
+
const mailDir = path.join(outputPath, `mail-${uid}`);
|
|
559
|
+
if (!fs.existsSync(mailDir)) {
|
|
560
|
+
fs.mkdirSync(mailDir, { recursive: true });
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ķ¹ģ ķģ¼ė§ ė¤ģ“ė”ė
|
|
564
|
+
if (fileIndex !== null) {
|
|
565
|
+
if (fileIndex < 0 || fileIndex >= mail.attachments.length) {
|
|
566
|
+
console.error(`ā Invalid file index. Use 1-${mail.attachments.length}`);
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const att = mail.attachments[fileIndex];
|
|
571
|
+
const filePath = path.join(mailDir, att.filename);
|
|
572
|
+
fs.writeFileSync(filePath, att.content);
|
|
573
|
+
|
|
574
|
+
const sizeKB = (att.size / 1024).toFixed(2);
|
|
575
|
+
console.log(`ā
Downloaded: ${att.filename} (${sizeKB} KB)`);
|
|
576
|
+
console.log(` Location: ${filePath}`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ėŖØė ķģ¼ ė¤ģ“ė”ė
|
|
581
|
+
console.log(`\nš Downloading ${mail.attachments.length} file(s)...\n`);
|
|
582
|
+
|
|
583
|
+
for (const att of mail.attachments) {
|
|
584
|
+
const filePath = path.join(mailDir, att.filename);
|
|
585
|
+
fs.writeFileSync(filePath, att.content);
|
|
586
|
+
|
|
587
|
+
const sizeKB = (att.size / 1024).toFixed(2);
|
|
588
|
+
console.log(`ā
${att.filename} (${sizeKB} KB)`);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
console.log(`\nš All files saved to: ${mailDir}`);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.error('ā Error:', error.message);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
} finally {
|
|
596
|
+
await client.close();
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
317
600
|
// send: ė©ģ¼ ė°ģ”
|
|
318
601
|
async function sendCommand(args) {
|
|
319
|
-
// ė¹ģøķ°ėķ°ėø ėŖØė: --to, --subject, --body ģµģ
ģ¬ģ©
|
|
320
|
-
let to, subject, body;
|
|
602
|
+
// ė¹ģøķ°ėķ°ėø ėŖØė: --to, --subject, --body, --cc, --bcc, --attach, --html ģµģ
ģ¬ģ©
|
|
603
|
+
let to, subject, body, cc, bcc, attachFiles = [], isHtml = false;
|
|
321
604
|
|
|
322
605
|
for (let i = 0; i < args.length; i++) {
|
|
323
606
|
if (args[i] === '--to' && args[i + 1]) {
|
|
@@ -326,6 +609,15 @@ async function sendCommand(args) {
|
|
|
326
609
|
subject = args[++i];
|
|
327
610
|
} else if (args[i] === '--body' && args[i + 1]) {
|
|
328
611
|
body = args[++i];
|
|
612
|
+
} else if (args[i] === '--cc' && args[i + 1]) {
|
|
613
|
+
cc = args[++i];
|
|
614
|
+
} else if (args[i] === '--bcc' && args[i + 1]) {
|
|
615
|
+
bcc = args[++i];
|
|
616
|
+
} else if (args[i] === '--attach' && args[i + 1]) {
|
|
617
|
+
attachFiles.push(args[++i]);
|
|
618
|
+
} else if (args[i] === '--html' && args[i + 1]) {
|
|
619
|
+
body = args[++i];
|
|
620
|
+
isHtml = true;
|
|
329
621
|
}
|
|
330
622
|
}
|
|
331
623
|
|
|
@@ -335,10 +627,43 @@ async function sendCommand(args) {
|
|
|
335
627
|
const client = await createClient(config);
|
|
336
628
|
|
|
337
629
|
try {
|
|
338
|
-
|
|
630
|
+
// 첨ė¶ķģ¼ ģ²ė¦¬
|
|
631
|
+
const attachments = [];
|
|
632
|
+
for (const filePath of attachFiles) {
|
|
633
|
+
if (!fs.existsSync(filePath)) {
|
|
634
|
+
console.error(`ā File not found: ${filePath}`);
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
attachments.push({
|
|
638
|
+
filename: path.basename(filePath),
|
|
639
|
+
content: fs.readFileSync(filePath)
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const mailParams = {
|
|
644
|
+
to,
|
|
645
|
+
subject,
|
|
646
|
+
cc: cc ? cc.split(',').map(e => e.trim()) : undefined,
|
|
647
|
+
bcc: bcc ? bcc.split(',').map(e => e.trim()) : undefined,
|
|
648
|
+
attachments: attachments.length > 0 ? attachments : undefined
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
if (isHtml) {
|
|
652
|
+
mailParams.html = true;
|
|
653
|
+
mailParams.body = body;
|
|
654
|
+
} else {
|
|
655
|
+
mailParams.text = body;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
await client.sendMail(mailParams);
|
|
339
659
|
console.log('ā
Mail sent successfully');
|
|
340
660
|
console.log(` To: ${to}`);
|
|
661
|
+
if (cc) console.log(` CC: ${cc}`);
|
|
662
|
+
if (bcc) console.log(` BCC: ${bcc}`);
|
|
341
663
|
console.log(` Subject: ${subject}`);
|
|
664
|
+
if (attachments.length > 0) {
|
|
665
|
+
console.log(` Attachments: ${attachments.length} file(s)`);
|
|
666
|
+
}
|
|
342
667
|
return;
|
|
343
668
|
} catch (error) {
|
|
344
669
|
console.error('ā Error:', error.message);
|
|
@@ -386,28 +711,50 @@ async function sendCommand(args) {
|
|
|
386
711
|
}
|
|
387
712
|
}
|
|
388
713
|
|
|
389
|
-
// search: ė©ģ¼ ź²ģ
|
|
390
|
-
async function searchCommand(
|
|
391
|
-
if (
|
|
392
|
-
console.error('ā Usage: dooray-cli search <
|
|
714
|
+
// search: ė©ģ¼ ź²ģ (ķ¤ģė, ė ģ§, ė°ģ ģ ķķ° ģ§ģ)
|
|
715
|
+
async function searchCommand(args) {
|
|
716
|
+
if (args.length === 0) {
|
|
717
|
+
console.error('ā Usage: dooray-cli search <keyword> [--from <email>] [--since <YYYY-MM-DD>] [--before <YYYY-MM-DD>]');
|
|
393
718
|
process.exit(1);
|
|
394
719
|
}
|
|
395
720
|
|
|
721
|
+
// ģµģ
ķģ±
|
|
722
|
+
let from, since, before;
|
|
723
|
+
const keywords = [];
|
|
724
|
+
|
|
725
|
+
for (let i = 0; i < args.length; i++) {
|
|
726
|
+
if (args[i] === '--from' && args[i + 1]) {
|
|
727
|
+
from = args[++i];
|
|
728
|
+
} else if (args[i] === '--since' && args[i + 1]) {
|
|
729
|
+
since = args[++i];
|
|
730
|
+
} else if (args[i] === '--before' && args[i + 1]) {
|
|
731
|
+
before = args[++i];
|
|
732
|
+
} else {
|
|
733
|
+
keywords.push(args[i]);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
396
737
|
const config = loadConfig();
|
|
397
738
|
const client = await createClient(config);
|
|
398
739
|
|
|
399
740
|
try {
|
|
400
|
-
const
|
|
401
|
-
const mails = await client.searchMail(criteria);
|
|
741
|
+
const mails = await client.searchMail(keywords, { from, since, before });
|
|
402
742
|
|
|
403
743
|
if (mails.length === 0) {
|
|
404
744
|
console.log('š No matching mails found');
|
|
405
745
|
return;
|
|
406
746
|
}
|
|
407
747
|
|
|
408
|
-
|
|
748
|
+
const filters = [];
|
|
749
|
+
if (keywords.length > 0) filters.push(`keywords: "${keywords.join(' ')}"`);
|
|
750
|
+
if (from) filters.push(`from: ${from}`);
|
|
751
|
+
if (since) filters.push(`since: ${since}`);
|
|
752
|
+
if (before) filters.push(`before: ${before}`);
|
|
753
|
+
|
|
754
|
+
console.log(`\nš Search Results (${filters.join(', ')}): ${mails.length} found\n`);
|
|
409
755
|
mails.slice(0, 10).forEach((mail, idx) => {
|
|
410
|
-
|
|
756
|
+
const fromText = typeof mail.from === 'object' ? `${mail.from.name} <${mail.from.email}>` : mail.from;
|
|
757
|
+
console.log(`${idx + 1}. [UID: ${mail.uid}] ${fromText}`);
|
|
411
758
|
console.log(` Subject: ${mail.subject}`);
|
|
412
759
|
console.log(` Date: ${mail.date}\n`);
|
|
413
760
|
});
|
|
@@ -463,10 +810,35 @@ Commands:
|
|
|
463
810
|
send Send a new mail (interactive)
|
|
464
811
|
send --to <email> --subject <subject> --body <body>
|
|
465
812
|
Send a mail (non-interactive)
|
|
813
|
+
send --to <email> --subject <subject> --body <body> --cc <email> --bcc <email>
|
|
814
|
+
Send with CC/BCC
|
|
815
|
+
send --to <email> --subject <subject> --html <html>
|
|
816
|
+
Send HTML mail
|
|
817
|
+
send --to <email> --subject <subject> --body <body> --attach <file>
|
|
818
|
+
Send with attachments
|
|
466
819
|
reply <uid> Reply to a mail (interactive)
|
|
467
820
|
reply <uid> --body <body>
|
|
468
821
|
Reply to a mail (non-interactive)
|
|
822
|
+
forward <uid> --to <email> [--body <message>]
|
|
823
|
+
Forward a mail
|
|
824
|
+
delete <uid> Delete a mail (move to trash)
|
|
825
|
+
delete <uid> --force
|
|
826
|
+
Permanently delete a mail
|
|
827
|
+
mark <uid> --read Mark a mail as read
|
|
828
|
+
mark <uid> --unread Mark a mail as unread
|
|
829
|
+
attachments <uid> List attachments in a mail
|
|
830
|
+
download <uid> Download all attachments
|
|
831
|
+
download <uid> --file <index>
|
|
832
|
+
Download a specific attachment
|
|
833
|
+
download <uid> --output <path>
|
|
834
|
+
Download to a custom path
|
|
469
835
|
search <keywords> Search mails by keywords
|
|
836
|
+
search <keywords> --from <email>
|
|
837
|
+
Search by sender
|
|
838
|
+
search <keywords> --since <YYYY-MM-DD>
|
|
839
|
+
Search since date
|
|
840
|
+
search <keywords> --before <YYYY-MM-DD>
|
|
841
|
+
Search before date
|
|
470
842
|
unread Show unread mail count
|
|
471
843
|
test Test IMAP/SMTP connection
|
|
472
844
|
help Show this help message
|
|
@@ -476,10 +848,23 @@ Examples:
|
|
|
476
848
|
dooray-cli list
|
|
477
849
|
dooray-cli recent
|
|
478
850
|
dooray-cli read 123
|
|
479
|
-
dooray-cli send --to "user@example.com" --subject "Hello" --body "Test
|
|
851
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --body "Test"
|
|
852
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --body "Test" --cc "cc@example.com" --attach "./file.pdf"
|
|
853
|
+
dooray-cli send --to "user@example.com" --subject "Hello" --html "<h1>Title</h1><p>Content</p>"
|
|
480
854
|
dooray-cli reply 123
|
|
481
855
|
dooray-cli reply 123 --body "Thank you for your email."
|
|
482
|
-
dooray-cli
|
|
856
|
+
dooray-cli forward 123 --to "another@example.com" --body "FYI"
|
|
857
|
+
dooray-cli delete 123
|
|
858
|
+
dooray-cli delete 123 --force
|
|
859
|
+
dooray-cli mark 123 --read
|
|
860
|
+
dooray-cli mark 123 --unread
|
|
861
|
+
dooray-cli attachments 123
|
|
862
|
+
dooray-cli download 123
|
|
863
|
+
dooray-cli download 123 --file 1
|
|
864
|
+
dooray-cli download 123 --output ./my-files
|
|
865
|
+
dooray-cli search "meeting" --from "boss@example.com"
|
|
866
|
+
dooray-cli search "report" --since "2026-01-01"
|
|
867
|
+
dooray-cli search "invoice" --before "2026-02-01"
|
|
483
868
|
dooray-cli unread
|
|
484
869
|
|
|
485
870
|
Configuration file: ${CONFIG_PATH}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dooray-mail-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Dooray mail CLI for OpenClaw Skill - IMAP/SMTP integration",
|
|
5
5
|
"main": "./dist/mail-client.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
],
|
|
24
24
|
"author": "Anonymous",
|
|
25
25
|
"license": "MIT",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"release:patch": "npm version patch && git push && git push --tags",
|
|
28
|
+
"release:minor": "npm version minor && git push && git push --tags",
|
|
29
|
+
"release:major": "npm version major && git push && git push --tags"
|
|
30
|
+
},
|
|
26
31
|
"dependencies": {
|
|
27
32
|
"crypto-js": "^4.2.0",
|
|
28
33
|
"imap-simple": "^5.1.0",
|