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 CHANGED
@@ -1,19 +1,112 @@
1
- # Dooray OpenClaw Skill
1
+ # Dooray Mail CLI
2
+
3
+ [![npm version](https://img.shields.io/npm/v/dooray-mail-cli.svg)](https://www.npmjs.com/package/dooray-mail-cli)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 # Setup
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
- See `SKILL.md` for OpenClaw integration.
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: "Manage Dooray emails via IMAP/SMTP using the dooray-cli command. List, read, send, and search emails from the terminal. Use when working with Dooray email, checking Dooray inbox, or when the user mentions Dooray mail operations."
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
- ### Search Emails
149
+ ### Forward an Email
150
150
 
151
- Search for emails by keywords in subject:
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" "update"
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
@@ -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(query, limit = 10) {
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
- // IMAP SEARCH: ģ œėŖ©ģ“ė‚˜ ė³øė¬øģ—ģ„œ ź²€ģƒ‰
282
- const searchCriteria = [
283
- 'OR',
284
- ['SUBJECT', query],
285
- ['BODY', query]
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
- console.log(`Attachments: ${mail.hasAttachments ? 'Yes' : 'No'}\n`);
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
- await client.sendMail({ to, subject, text: body });
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(keywords) {
391
- if (keywords.length === 0) {
392
- console.error('āŒ Usage: dooray-cli search <keyword1> [keyword2] ...');
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 criteria = keywords.map(k => ['SUBJECT', k]);
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
- console.log(`\nšŸ” Search Results (${mails.length})\n`);
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
- console.log(`${idx + 1}. [UID: ${mail.uid}] ${mail.from}`);
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 message"
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 search "meeting" "report"
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.1.2",
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",