@bifos/dooray-cli 0.1.1 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +48 -0
  2. package/dist/index.js +619 -38
  3. package/package.json +6 -1
package/README.md CHANGED
@@ -82,6 +82,54 @@ dooray wiki pages tc-ocr # 페이지 목록
82
82
  dooray wiki page get tc-ocr <page-id> # 페이지 상세
83
83
  ```
84
84
 
85
+ ### 메일
86
+
87
+ IMAP을 통해 Dooray 메일을 조회할 수 있습니다.
88
+
89
+ ```bash
90
+ # 초기 설정 (서버 정보는 기본값 제공)
91
+ dooray config set imap-username your@email.com
92
+ dooray config set imap-password <IMAP_APP_PASSWORD>
93
+
94
+ # 메일 조회
95
+ dooray mail list # 최근 메일 목록
96
+ dooray mail list --unread # 안읽은 메일만
97
+ dooray mail list --search "키워드" # 제목 검색
98
+ dooray mail list --size 50 # 조회 개수 지정
99
+ dooray mail get <uid> # 메일 상세
100
+ dooray mail get <uid> --json # JSON 출력
101
+
102
+ # 메일 발송
103
+ dooray mail send --to "user@example.com" --subject "제목" --body "본문"
104
+ dooray mail send --to "a@b.com" --cc "c@d.com" --subject "제목" --body-file ./content.md
105
+ dooray mail send --to "a@b.com" --subject "HTML 메일" --body "<h1>Hello</h1>" --html
106
+
107
+ # 메일 답장 (스레드 유지)
108
+ dooray mail reply <uid> --body "답장 내용"
109
+ ```
110
+
111
+ ### 첨부파일
112
+
113
+ 업무에 파일을 첨부하거나, 첨부된 파일을 다운로드할 수 있습니다.
114
+
115
+ ```bash
116
+ # 첨부파일 목록
117
+ dooray post file list <project> <number>
118
+
119
+ # 파일 다운로드
120
+ dooray post file download <project> <number> <file-id>
121
+ dooray post file download <project> <number> <file-id> -o ./downloads
122
+
123
+ # 전체 파일 다운로드
124
+ dooray post file download-all <project> <number> -o ./downloads
125
+
126
+ # 파일 업로드
127
+ dooray post file upload <project> <number> ./report.pdf
128
+
129
+ # 파일 삭제
130
+ dooray post file delete <project> <number> <file-id>
131
+ ```
132
+
85
133
  ## 출력 모드
86
134
 
87
135
  | 플래그 | 설명 | 용도 |
package/dist/index.js CHANGED
@@ -24,8 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_commander23 = require("commander");
28
- var import_chalk4 = __toESM(require("chalk"));
27
+ var import_commander32 = require("commander");
28
+ var import_chalk6 = __toESM(require("chalk"));
29
29
 
30
30
  // src/commands/config.ts
31
31
  var import_commander = require("commander");
@@ -52,6 +52,15 @@ var EXIT_AUTH_ERROR = 2;
52
52
  var EXIT_PARAM_ERROR = 3;
53
53
  var EXIT_CONFIG_ERROR = 4;
54
54
 
55
+ // src/config/types.ts
56
+ var DEFAULTS = {
57
+ baseUrl: "https://api.dooray.com",
58
+ imapHost: "imap.dooray.com",
59
+ imapPort: 993,
60
+ smtpHost: "smtp.dooray.com",
61
+ smtpPort: 465
62
+ };
63
+
55
64
  // src/config/store.ts
56
65
  var DOORAY_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".dooray");
57
66
  var CONFIG_PATH = (0, import_node_path.join)(DOORAY_DIR, "config.json");
@@ -81,7 +90,7 @@ async function setConfigValue(key, value) {
81
90
  const config = await getConfig() ?? {
82
91
  version: 1,
83
92
  apiKey: "",
84
- baseUrl: ""
93
+ baseUrl: DEFAULTS.baseUrl
85
94
  };
86
95
  switch (key) {
87
96
  case "api-key":
@@ -90,10 +99,28 @@ async function setConfigValue(key, value) {
90
99
  case "base-url":
91
100
  config.baseUrl = value;
92
101
  break;
102
+ case "imap-host":
103
+ config.imapHost = value;
104
+ break;
105
+ case "imap-port":
106
+ config.imapPort = parseInt(value, 10);
107
+ break;
108
+ case "imap-username":
109
+ config.imapUsername = value;
110
+ break;
111
+ case "imap-password":
112
+ config.imapPassword = value;
113
+ break;
114
+ case "smtp-host":
115
+ config.smtpHost = value;
116
+ break;
117
+ case "smtp-port":
118
+ config.smtpPort = parseInt(value, 10);
119
+ break;
93
120
  default:
94
121
  throw new DoorayCliError(
95
122
  `\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${key}
96
- \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url`,
123
+ \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url, imap-host, imap-port, imap-username, imap-password, smtp-host, smtp-port`,
97
124
  EXIT_CONFIG_ERROR
98
125
  );
99
126
  }
@@ -251,6 +278,8 @@ var import_chalk3 = __toESM(require("chalk"));
251
278
 
252
279
  // src/api/client.ts
253
280
  var import_ky = __toESM(require("ky"));
281
+ var import_promises3 = require("fs/promises");
282
+ var import_node_path3 = require("path");
254
283
  function joinIds(ids) {
255
284
  return ids && ids.length > 0 ? ids.join(",") : void 0;
256
285
  }
@@ -276,11 +305,15 @@ async function toDoorayCliError(error) {
276
305
  }
277
306
  var DoorayApiClient = class {
278
307
  api;
308
+ authHeader;
309
+ baseUrl;
279
310
  constructor(apiKey, baseUrl) {
311
+ this.authHeader = `dooray-api ${apiKey}`;
312
+ this.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
280
313
  this.api = import_ky.default.create({
281
314
  prefixUrl: baseUrl,
282
315
  headers: {
283
- Authorization: `dooray-api ${apiKey}`
316
+ Authorization: this.authHeader
284
317
  }
285
318
  });
286
319
  }
@@ -491,6 +524,97 @@ var DoorayApiClient = class {
491
524
  return toDoorayCliError(e);
492
525
  }
493
526
  }
527
+ // ─── Post Files ─────────────────────────────────────
528
+ async getPostFiles(projectId, postId) {
529
+ try {
530
+ return await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/files`).json();
531
+ } catch (e) {
532
+ return toDoorayCliError(e);
533
+ }
534
+ }
535
+ async getPostFileMeta(projectId, postId, fileId) {
536
+ try {
537
+ return await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/files/${fileId}`, {
538
+ searchParams: { media: "meta" }
539
+ }).json();
540
+ } catch (e) {
541
+ return toDoorayCliError(e);
542
+ }
543
+ }
544
+ async downloadPostFile(projectId, postId, fileId) {
545
+ try {
546
+ const res = await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/files/${fileId}`, {
547
+ searchParams: { media: "raw" },
548
+ redirect: "manual",
549
+ throwHttpErrors: false
550
+ });
551
+ const location = res.headers.get("location");
552
+ if (!location) {
553
+ throw new DoorayCliError("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uB9AC\uB2E4\uC774\uB809\uD2B8 URL\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", EXIT_API_ERROR);
554
+ }
555
+ const fileRes = await fetch(location, {
556
+ headers: { Authorization: this.authHeader }
557
+ });
558
+ if (!fileRes.ok) {
559
+ throw new DoorayCliError(`\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328 (${fileRes.status})`, EXIT_API_ERROR);
560
+ }
561
+ const disposition = fileRes.headers.get("content-disposition");
562
+ let fileName = `file-${fileId}`;
563
+ if (disposition) {
564
+ const match = disposition.match(/filename\*?=(?:UTF-8''|"?)([^";]+)/i);
565
+ if (match) fileName = decodeURIComponent(match[1].replace(/"/g, ""));
566
+ }
567
+ const buffer = await fileRes.arrayBuffer();
568
+ return { buffer, fileName };
569
+ } catch (e) {
570
+ if (e instanceof DoorayCliError) throw e;
571
+ return toDoorayCliError(e);
572
+ }
573
+ }
574
+ async uploadPostFile(projectId, postId, filePath) {
575
+ try {
576
+ const fileName = (0, import_node_path3.basename)(filePath);
577
+ const fileBuffer = await (0, import_promises3.readFile)(filePath);
578
+ const formData = new FormData();
579
+ formData.append("file", new Blob([fileBuffer]), fileName);
580
+ const url = `${this.baseUrl}project/v1/projects/${projectId}/posts/${postId}/files`;
581
+ const res = await fetch(url, {
582
+ method: "POST",
583
+ headers: { Authorization: this.authHeader },
584
+ body: formData,
585
+ redirect: "manual"
586
+ });
587
+ if (res.status === 307) {
588
+ const location = res.headers.get("location");
589
+ if (!location) {
590
+ throw new DoorayCliError("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uB9AC\uB2E4\uC774\uB809\uD2B8 URL\uC744 \uBC1B\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", EXIT_API_ERROR);
591
+ }
592
+ const uploadRes = await fetch(location, {
593
+ method: "POST",
594
+ headers: { Authorization: this.authHeader },
595
+ body: formData
596
+ });
597
+ if (!uploadRes.ok) {
598
+ throw new DoorayCliError(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${uploadRes.status})`, EXIT_API_ERROR);
599
+ }
600
+ return await uploadRes.json();
601
+ }
602
+ if (!res.ok) {
603
+ throw new DoorayCliError(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${res.status})`, EXIT_API_ERROR);
604
+ }
605
+ return await res.json();
606
+ } catch (e) {
607
+ if (e instanceof DoorayCliError) throw e;
608
+ return toDoorayCliError(e);
609
+ }
610
+ }
611
+ async deletePostFile(projectId, postId, fileId) {
612
+ try {
613
+ return await this.api.delete(`project/v1/projects/${projectId}/posts/${postId}/files/${fileId}`).json();
614
+ } catch (e) {
615
+ return toDoorayCliError(e);
616
+ }
617
+ }
494
618
  };
495
619
 
496
620
  // src/cache/types.ts
@@ -915,11 +1039,11 @@ var postGetCommand = new import_commander9.Command("get").description("\uC5C5\uB
915
1039
 
916
1040
  // src/commands/post/edit.ts
917
1041
  var import_commander10 = require("commander");
918
- var import_promises4 = __toESM(require("fs/promises"));
1042
+ var import_promises5 = __toESM(require("fs/promises"));
919
1043
 
920
1044
  // src/editor/index.ts
921
1045
  var import_node_child_process = require("child_process");
922
- var import_promises3 = __toESM(require("fs/promises"));
1046
+ var import_promises4 = __toESM(require("fs/promises"));
923
1047
  var import_tmp = __toESM(require("tmp"));
924
1048
  var import_js_yaml = __toESM(require("js-yaml"));
925
1049
  function openInEditor(content) {
@@ -933,7 +1057,7 @@ function openInEditor(content) {
933
1057
  const tmpFile = import_tmp.default.fileSync({ prefix: "dooray-", postfix: ".md" });
934
1058
  return new Promise(async (resolve, reject) => {
935
1059
  try {
936
- await import_promises3.default.writeFile(tmpFile.name, content, "utf-8");
1060
+ await import_promises4.default.writeFile(tmpFile.name, content, "utf-8");
937
1061
  const child = (0, import_node_child_process.spawn)(editor, [tmpFile.name], {
938
1062
  stdio: "inherit"
939
1063
  });
@@ -949,7 +1073,7 @@ function openInEditor(content) {
949
1073
  EXIT_PARAM_ERROR
950
1074
  );
951
1075
  }
952
- const result = await import_promises3.default.readFile(tmpFile.name, "utf-8");
1076
+ const result = await import_promises4.default.readFile(tmpFile.name, "utf-8");
953
1077
  resolve(result);
954
1078
  } catch (e) {
955
1079
  reject(e);
@@ -1071,7 +1195,7 @@ async function resolveBody(opts) {
1071
1195
  }
1072
1196
  if (opts.bodyFile) {
1073
1197
  if (opts.bodyFile === "-") return readStdin();
1074
- return import_promises4.default.readFile(opts.bodyFile, "utf-8");
1198
+ return import_promises5.default.readFile(opts.bodyFile, "utf-8");
1075
1199
  }
1076
1200
  return null;
1077
1201
  }
@@ -1140,13 +1264,13 @@ var postEditCommand = new import_commander10.Command("edit").description("\uC5C5
1140
1264
 
1141
1265
  // src/commands/post/create.ts
1142
1266
  var import_commander11 = require("commander");
1143
- var import_promises5 = __toESM(require("fs/promises"));
1267
+ var import_promises6 = __toESM(require("fs/promises"));
1144
1268
  async function readBody(opts) {
1145
1269
  if (opts.bodyFile) {
1146
1270
  if (opts.bodyFile === "-") {
1147
1271
  return readStdin2();
1148
1272
  }
1149
- return import_promises5.default.readFile(opts.bodyFile, "utf-8");
1273
+ return import_promises6.default.readFile(opts.bodyFile, "utf-8");
1150
1274
  }
1151
1275
  if (opts.body === "-") {
1152
1276
  return readStdin2();
@@ -1249,7 +1373,7 @@ var commentListCommand = new import_commander14.Command("list").description("\uB
1249
1373
 
1250
1374
  // src/commands/post/comment/add.ts
1251
1375
  var import_commander15 = require("commander");
1252
- var import_promises6 = __toESM(require("fs/promises"));
1376
+ var import_promises7 = __toESM(require("fs/promises"));
1253
1377
  async function readStdin3() {
1254
1378
  if (process.stdin.isTTY) {
1255
1379
  throw new DoorayCliError(
@@ -1270,7 +1394,7 @@ async function resolveBody2(opts) {
1270
1394
  }
1271
1395
  if (opts.bodyFile) {
1272
1396
  if (opts.bodyFile === "-") return readStdin3();
1273
- return import_promises6.default.readFile(opts.bodyFile, "utf-8");
1397
+ return import_promises7.default.readFile(opts.bodyFile, "utf-8");
1274
1398
  }
1275
1399
  return null;
1276
1400
  }
@@ -1305,7 +1429,7 @@ var commentAddCommand = new import_commander15.Command("add").description("\uB31
1305
1429
 
1306
1430
  // src/commands/post/comment/edit.ts
1307
1431
  var import_commander16 = require("commander");
1308
- var import_promises7 = __toESM(require("fs/promises"));
1432
+ var import_promises8 = __toESM(require("fs/promises"));
1309
1433
  async function readStdin4() {
1310
1434
  if (process.stdin.isTTY) {
1311
1435
  throw new DoorayCliError(
@@ -1326,7 +1450,7 @@ async function resolveBody3(opts) {
1326
1450
  }
1327
1451
  if (opts.bodyFile) {
1328
1452
  if (opts.bodyFile === "-") return readStdin4();
1329
- return import_promises7.default.readFile(opts.bodyFile, "utf-8");
1453
+ return import_promises8.default.readFile(opts.bodyFile, "utf-8");
1330
1454
  }
1331
1455
  return null;
1332
1456
  }
@@ -1376,8 +1500,125 @@ var commentDeleteCommand = new import_commander17.Command("delete").description(
1376
1500
  `);
1377
1501
  });
1378
1502
 
1379
- // src/commands/wiki/list.ts
1503
+ // src/commands/post/file/list.ts
1380
1504
  var import_commander18 = require("commander");
1505
+ function formatSize(bytes) {
1506
+ if (bytes < 1024) return `${bytes}B`;
1507
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
1508
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1509
+ }
1510
+ var fileListCommand = new import_commander18.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").action(async (project, postNumberStr) => {
1511
+ const globalOpts = fileListCommand.optsWithGlobals();
1512
+ const config = await getConfigOrThrow();
1513
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1514
+ startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
1515
+ const projectId = await resolveProject(client, project);
1516
+ const postId = await resolvePost(client, projectId, Number(postNumberStr));
1517
+ const res = await client.getPostFiles(projectId, postId);
1518
+ stopSpinner(true, `\uCCA8\uBD80\uD30C\uC77C ${res.result.length}\uAC1C`);
1519
+ output(globalOpts, {
1520
+ headers: ["ID", "\uD30C\uC77C\uBA85", "\uD06C\uAE30", "MIME", "\uC0DD\uC131\uC77C"],
1521
+ rows: res.result.map((f) => [
1522
+ f.id,
1523
+ f.name,
1524
+ formatSize(f.size),
1525
+ f.mimeType,
1526
+ f.createdAt
1527
+ ]),
1528
+ raw: res.result,
1529
+ ids: res.result.map((f) => f.id)
1530
+ });
1531
+ });
1532
+
1533
+ // src/commands/post/file/download.ts
1534
+ var import_commander19 = require("commander");
1535
+ var import_promises9 = require("fs/promises");
1536
+ var import_node_path4 = require("path");
1537
+ var fileDownloadCommand = new import_commander19.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-id>", "\uD30C\uC77C ID").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, fileId, opts) => {
1538
+ const config = await getConfigOrThrow();
1539
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1540
+ startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1541
+ const projectId = await resolveProject(client, project);
1542
+ const postId = await resolvePost(client, projectId, Number(postNumberStr));
1543
+ const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
1544
+ const outputPath = (0, import_node_path4.join)(opts.output, fileName);
1545
+ await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
1546
+ stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
1547
+ process.stdout.write(`${outputPath}
1548
+ `);
1549
+ });
1550
+
1551
+ // src/commands/post/file/download-all.ts
1552
+ var import_commander20 = require("commander");
1553
+ var import_promises10 = require("fs/promises");
1554
+ var import_node_path5 = require("path");
1555
+ var fileDownloadAllCommand = new import_commander20.Command("download-all").description("\uC5C5\uBB34\uC758 \uBAA8\uB4E0 \uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
1556
+ const config = await getConfigOrThrow();
1557
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1558
+ const spinner = startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
1559
+ const projectId = await resolveProject(client, project);
1560
+ const postId = await resolvePost(client, projectId, Number(postNumberStr));
1561
+ const res = await client.getPostFiles(projectId, postId);
1562
+ if (res.result.length === 0) {
1563
+ stopSpinner(true, "\uCCA8\uBD80\uD30C\uC77C \uC5C6\uC74C");
1564
+ process.stdout.write("\uCCA8\uBD80\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
1565
+ return;
1566
+ }
1567
+ await (0, import_promises10.mkdir)(opts.output, { recursive: true });
1568
+ const downloaded = [];
1569
+ for (const file of res.result) {
1570
+ spinner.text = `\uB2E4\uC6B4\uB85C\uB4DC \uC911: ${file.name} (${downloaded.length + 1}/${res.result.length})`;
1571
+ const { buffer, fileName } = await client.downloadPostFile(projectId, postId, file.id);
1572
+ const outputPath = (0, import_node_path5.join)(opts.output, fileName);
1573
+ await (0, import_promises10.writeFile)(outputPath, Buffer.from(buffer));
1574
+ downloaded.push(outputPath);
1575
+ }
1576
+ stopSpinner(true, `${downloaded.length}\uAC1C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC`);
1577
+ for (const path of downloaded) {
1578
+ process.stdout.write(`${path}
1579
+ `);
1580
+ }
1581
+ });
1582
+
1583
+ // src/commands/post/file/upload.ts
1584
+ var import_commander21 = require("commander");
1585
+ var import_node_path6 = require("path");
1586
+ var fileUploadCommand = new import_commander21.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C").action(async (project, postNumberStr, filePath) => {
1587
+ const globalOpts = fileUploadCommand.optsWithGlobals();
1588
+ const config = await getConfigOrThrow();
1589
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1590
+ startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
1591
+ const projectId = await resolveProject(client, project);
1592
+ const postId = await resolvePost(client, projectId, Number(postNumberStr));
1593
+ const res = await client.uploadPostFile(projectId, postId, filePath);
1594
+ stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
1595
+ if (globalOpts.json) {
1596
+ printJson(res.result);
1597
+ } else if (globalOpts.quiet) {
1598
+ process.stdout.write(`${res.result.id}
1599
+ `);
1600
+ } else {
1601
+ process.stdout.write(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${(0, import_node_path6.basename)(filePath)} (ID: ${res.result.id})
1602
+ `);
1603
+ }
1604
+ });
1605
+
1606
+ // src/commands/post/file/delete.ts
1607
+ var import_commander22 = require("commander");
1608
+ var fileDeleteCommand = new import_commander22.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-id>", "\uD30C\uC77C ID").action(async (project, postNumberStr, fileId) => {
1609
+ const config = await getConfigOrThrow();
1610
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1611
+ startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
1612
+ const projectId = await resolveProject(client, project);
1613
+ const postId = await resolvePost(client, projectId, Number(postNumberStr));
1614
+ await client.deletePostFile(projectId, postId, fileId);
1615
+ stopSpinner(true, "\uC0AD\uC81C \uC644\uB8CC");
1616
+ process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1617
+ `);
1618
+ });
1619
+
1620
+ // src/commands/wiki/list.ts
1621
+ var import_commander23 = require("commander");
1381
1622
 
1382
1623
  // src/formatters/wiki.ts
1383
1624
  function formatWikiList(wikis, opts) {
@@ -1420,7 +1661,7 @@ function formatWikiPageDetail(page, opts) {
1420
1661
  }
1421
1662
 
1422
1663
  // src/commands/wiki/list.ts
1423
- var wikiListCommand = new import_commander18.Command("list").description("\uC704\uD0A4 \uBAA9\uB85D \uC870\uD68C").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (opts) => {
1664
+ var wikiListCommand = new import_commander23.Command("list").description("\uC704\uD0A4 \uBAA9\uB85D \uC870\uD68C").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (opts) => {
1424
1665
  const globalOpts = wikiListCommand.optsWithGlobals();
1425
1666
  const config = await getConfigOrThrow();
1426
1667
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1434,7 +1675,7 @@ var wikiListCommand = new import_commander18.Command("list").description("\uC704
1434
1675
  });
1435
1676
 
1436
1677
  // src/commands/wiki/pages.ts
1437
- var import_commander19 = require("commander");
1678
+ var import_commander24 = require("commander");
1438
1679
 
1439
1680
  // src/resolvers/wiki.ts
1440
1681
  async function resolveWiki(client, projectCode) {
@@ -1453,7 +1694,7 @@ async function resolveWiki(client, projectCode) {
1453
1694
  }
1454
1695
 
1455
1696
  // src/commands/wiki/pages.ts
1456
- var wikiPagesCommand = new import_commander19.Command("pages").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").action(async (project, opts) => {
1697
+ var wikiPagesCommand = new import_commander24.Command("pages").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").action(async (project, opts) => {
1457
1698
  const globalOpts = wikiPagesCommand.optsWithGlobals();
1458
1699
  const config = await getConfigOrThrow();
1459
1700
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1465,8 +1706,8 @@ var wikiPagesCommand = new import_commander19.Command("pages").description("\uC7
1465
1706
  });
1466
1707
 
1467
1708
  // src/commands/wiki/page-get.ts
1468
- var import_commander20 = require("commander");
1469
- var wikiPageGetCommand = new import_commander20.Command("get").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0C1\uC138 \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
1709
+ var import_commander25 = require("commander");
1710
+ var wikiPageGetCommand = new import_commander25.Command("get").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0C1\uC138 \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
1470
1711
  const globalOpts = wikiPageGetCommand.optsWithGlobals();
1471
1712
  const config = await getConfigOrThrow();
1472
1713
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1478,14 +1719,14 @@ var wikiPageGetCommand = new import_commander20.Command("get").description("\uC7
1478
1719
  });
1479
1720
 
1480
1721
  // src/commands/wiki/page-create.ts
1481
- var import_commander21 = require("commander");
1482
- var import_promises8 = __toESM(require("fs/promises"));
1722
+ var import_commander26 = require("commander");
1723
+ var import_promises11 = __toESM(require("fs/promises"));
1483
1724
  async function readBody2(opts) {
1484
1725
  if (opts.bodyFile) {
1485
1726
  if (opts.bodyFile === "-") {
1486
1727
  return readStdin5();
1487
1728
  }
1488
- return import_promises8.default.readFile(opts.bodyFile, "utf-8");
1729
+ return import_promises11.default.readFile(opts.bodyFile, "utf-8");
1489
1730
  }
1490
1731
  if (opts.body === "-") {
1491
1732
  return readStdin5();
@@ -1505,7 +1746,7 @@ async function readStdin5() {
1505
1746
  }
1506
1747
  return Buffer.concat(chunks).toString("utf-8");
1507
1748
  }
1508
- var wikiPageCreateCommand = new import_commander21.Command("create").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, opts) => {
1749
+ var wikiPageCreateCommand = new import_commander26.Command("create").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, opts) => {
1509
1750
  const globalOpts = wikiPageCreateCommand.optsWithGlobals();
1510
1751
  const config = await getConfigOrThrow();
1511
1752
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1529,8 +1770,8 @@ var wikiPageCreateCommand = new import_commander21.Command("create").description
1529
1770
  });
1530
1771
 
1531
1772
  // src/commands/wiki/page-edit.ts
1532
- var import_commander22 = require("commander");
1533
- var wikiPageEditCommand = new import_commander22.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 ($EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
1773
+ var import_commander27 = require("commander");
1774
+ var wikiPageEditCommand = new import_commander27.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 ($EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
1534
1775
  const config = await getConfigOrThrow();
1535
1776
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1536
1777
  startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC870\uD68C \uC911...");
@@ -1555,20 +1796,347 @@ var wikiPageEditCommand = new import_commander22.Command("edit").description("\u
1555
1796
  `);
1556
1797
  });
1557
1798
 
1799
+ // src/commands/mail/list.ts
1800
+ var import_commander28 = require("commander");
1801
+
1802
+ // src/api/imapClient.ts
1803
+ var import_imapflow = require("imapflow");
1804
+ var import_mailparser = require("mailparser");
1805
+ function getImapConfigOrThrow(config) {
1806
+ if (!config.imapUsername || !config.imapPassword) {
1807
+ throw new DoorayCliError(
1808
+ "IMAP \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uC124\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694:\n dooray config set imap-username <YOUR_EMAIL>\n dooray config set imap-password <YOUR_IMAP_PASSWORD>",
1809
+ EXIT_CONFIG_ERROR
1810
+ );
1811
+ }
1812
+ return {
1813
+ host: config.imapHost ?? DEFAULTS.imapHost,
1814
+ port: config.imapPort ?? DEFAULTS.imapPort,
1815
+ username: config.imapUsername,
1816
+ password: config.imapPassword
1817
+ };
1818
+ }
1819
+ function createClient(config) {
1820
+ const imap = getImapConfigOrThrow(config);
1821
+ return new import_imapflow.ImapFlow({
1822
+ host: imap.host,
1823
+ port: imap.port,
1824
+ secure: true,
1825
+ auth: { user: imap.username, pass: imap.password },
1826
+ logger: false
1827
+ });
1828
+ }
1829
+ async function listMails(config, opts) {
1830
+ const client = createClient(config);
1831
+ const limit = opts.limit ?? 20;
1832
+ try {
1833
+ await client.connect();
1834
+ const lock = await client.getMailboxLock("INBOX");
1835
+ try {
1836
+ const query = {};
1837
+ if (opts.unread) query.seen = false;
1838
+ if (opts.search) query.subject = opts.search;
1839
+ if (Object.keys(query).length === 0) query.all = true;
1840
+ const uids = await client.search(query, { uid: true });
1841
+ if (uids.length === 0) return [];
1842
+ const sorted = uids.sort((a, b) => b - a).slice(0, limit);
1843
+ const uidSet = sorted.join(",");
1844
+ const messages = [];
1845
+ for await (const msg of client.fetch(uidSet, {
1846
+ uid: true,
1847
+ flags: true,
1848
+ envelope: true
1849
+ }, { uid: true })) {
1850
+ messages.push({
1851
+ uid: msg.uid,
1852
+ subject: msg.envelope.subject ?? "(\uC81C\uBAA9 \uC5C6\uC74C)",
1853
+ from: msg.envelope.from?.[0] ? `${msg.envelope.from[0].name || ""} <${msg.envelope.from[0].address || ""}>` : "(unknown)",
1854
+ to: (msg.envelope.to ?? []).map(
1855
+ (t) => `${t.name || ""} <${t.address || ""}>`
1856
+ ),
1857
+ date: msg.envelope.date ?? null,
1858
+ isRead: msg.flags.has("\\Seen")
1859
+ });
1860
+ }
1861
+ messages.sort((a, b) => b.uid - a.uid);
1862
+ return messages;
1863
+ } finally {
1864
+ lock.release();
1865
+ }
1866
+ } finally {
1867
+ await client.logout();
1868
+ }
1869
+ }
1870
+ async function getMail(config, uid) {
1871
+ const client = createClient(config);
1872
+ try {
1873
+ await client.connect();
1874
+ const lock = await client.getMailboxLock("INBOX");
1875
+ try {
1876
+ const msg = await client.fetchOne(String(uid), {
1877
+ uid: true,
1878
+ flags: true,
1879
+ envelope: true,
1880
+ source: true
1881
+ }, { uid: true });
1882
+ if (!msg) {
1883
+ throw new DoorayCliError(`\uBA54\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: UID ${uid}`, 1);
1884
+ }
1885
+ const parsed = await (0, import_mailparser.simpleParser)(msg.source);
1886
+ const body = parsed.text ?? parsed.html ?? "(\uBCF8\uBB38 \uC5C6\uC74C)";
1887
+ return {
1888
+ uid: msg.uid,
1889
+ subject: msg.envelope.subject ?? "(\uC81C\uBAA9 \uC5C6\uC74C)",
1890
+ from: msg.envelope.from?.[0] ? `${msg.envelope.from[0].name || ""} <${msg.envelope.from[0].address || ""}>` : "(unknown)",
1891
+ to: (msg.envelope.to ?? []).map(
1892
+ (t) => `${t.name || ""} <${t.address || ""}>`
1893
+ ),
1894
+ date: msg.envelope.date ?? null,
1895
+ isRead: msg.flags.has("\\Seen"),
1896
+ body
1897
+ };
1898
+ } finally {
1899
+ lock.release();
1900
+ }
1901
+ } finally {
1902
+ await client.logout();
1903
+ }
1904
+ }
1905
+
1906
+ // src/commands/mail/list.ts
1907
+ var import_chalk4 = __toESM(require("chalk"));
1908
+ var mailListCommand = new import_commander28.Command("list").description("\uBA54\uC77C \uBAA9\uB85D \uC870\uD68C").option("--unread", "\uC548\uC77D\uC740 \uBA54\uC77C\uB9CC").option("--search <keyword>", "\uC81C\uBAA9 \uAC80\uC0C9").option("--size <number>", "\uC870\uD68C \uAC1C\uC218", "20").action(async (opts) => {
1909
+ const globalOpts = mailListCommand.optsWithGlobals();
1910
+ const config = await getConfigOrThrow();
1911
+ startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
1912
+ const mails = await listMails(config, {
1913
+ unread: opts.unread,
1914
+ search: opts.search,
1915
+ limit: Number(opts.size)
1916
+ });
1917
+ stopSpinner(true, `\uBA54\uC77C ${mails.length}\uAC74 \uC870\uD68C \uC644\uB8CC`);
1918
+ output(globalOpts, {
1919
+ headers: ["UID", "\uC77D\uC74C", "\uB0A0\uC9DC", "\uBCF4\uB0B8\uC0AC\uB78C", "\uC81C\uBAA9"],
1920
+ rows: mails.map((m) => [
1921
+ String(m.uid),
1922
+ m.isRead ? "\u2713" : import_chalk4.default.bold("\u25CF"),
1923
+ m.date ? m.date.toLocaleDateString("ko-KR") : "",
1924
+ m.from.replace(/<.*>/, "").trim() || m.from,
1925
+ m.subject
1926
+ ]),
1927
+ raw: mails.map((m) => ({
1928
+ uid: m.uid,
1929
+ subject: m.subject,
1930
+ from: m.from,
1931
+ to: m.to,
1932
+ date: m.date?.toISOString() ?? null,
1933
+ isRead: m.isRead
1934
+ })),
1935
+ ids: mails.map((m) => String(m.uid))
1936
+ });
1937
+ });
1938
+
1939
+ // src/commands/mail/get.ts
1940
+ var import_commander29 = require("commander");
1941
+ var import_chalk5 = __toESM(require("chalk"));
1942
+ var mailGetCommand = new import_commander29.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
1943
+ const globalOpts = mailGetCommand.optsWithGlobals();
1944
+ const config = await getConfigOrThrow();
1945
+ startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
1946
+ const mail = await getMail(config, Number(uid));
1947
+ stopSpinner(true, "\uBA54\uC77C \uC870\uD68C \uC644\uB8CC");
1948
+ if (globalOpts.json) {
1949
+ printJson({
1950
+ uid: mail.uid,
1951
+ subject: mail.subject,
1952
+ from: mail.from,
1953
+ to: mail.to,
1954
+ date: mail.date?.toISOString() ?? null,
1955
+ isRead: mail.isRead,
1956
+ body: mail.body
1957
+ });
1958
+ } else {
1959
+ process.stdout.write(
1960
+ `${import_chalk5.default.bold("\uC81C\uBAA9:")} ${mail.subject}
1961
+ ${import_chalk5.default.bold("\uBCF4\uB0B8\uC0AC\uB78C:")} ${mail.from}
1962
+ ${import_chalk5.default.bold("\uBC1B\uB294\uC0AC\uB78C:")} ${mail.to.join(", ")}
1963
+ ${import_chalk5.default.bold("\uB0A0\uC9DC:")} ${mail.date?.toLocaleString("ko-KR") ?? ""}
1964
+ ${import_chalk5.default.bold("\uC77D\uC74C:")} ${mail.isRead ? "\uC608" : "\uC544\uB2C8\uC624"}
1965
+
1966
+ ${mail.body}
1967
+ `
1968
+ );
1969
+ }
1970
+ });
1971
+
1972
+ // src/commands/mail/send.ts
1973
+ var import_commander30 = require("commander");
1974
+ var import_promises12 = require("fs/promises");
1975
+
1976
+ // src/api/smtpClient.ts
1977
+ var import_nodemailer = __toESM(require("nodemailer"));
1978
+ function getSmtpConfigOrThrow(config) {
1979
+ if (!config.imapUsername || !config.imapPassword) {
1980
+ throw new DoorayCliError(
1981
+ "\uBA54\uC77C \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uC124\uC815\uC744 \uC9C4\uD589\uD558\uC138\uC694:\n dooray config set imap-username <YOUR_EMAIL>\n dooray config set imap-password <YOUR_IMAP_PASSWORD>",
1982
+ EXIT_CONFIG_ERROR
1983
+ );
1984
+ }
1985
+ return {
1986
+ host: config.smtpHost ?? DEFAULTS.smtpHost,
1987
+ port: config.smtpPort ?? DEFAULTS.smtpPort,
1988
+ username: config.imapUsername,
1989
+ password: config.imapPassword
1990
+ };
1991
+ }
1992
+ async function sendMail(config, opts) {
1993
+ const smtp = getSmtpConfigOrThrow(config);
1994
+ const transporter = import_nodemailer.default.createTransport({
1995
+ host: smtp.host,
1996
+ port: smtp.port,
1997
+ secure: true,
1998
+ auth: { user: smtp.username, pass: smtp.password }
1999
+ });
2000
+ const mailOptions = {
2001
+ from: smtp.username,
2002
+ to: opts.to.join(", "),
2003
+ subject: opts.subject,
2004
+ inReplyTo: opts.inReplyTo,
2005
+ references: opts.references
2006
+ };
2007
+ if (opts.cc?.length) mailOptions.cc = opts.cc.join(", ");
2008
+ if (opts.bcc?.length) mailOptions.bcc = opts.bcc.join(", ");
2009
+ if (opts.html) {
2010
+ mailOptions.html = opts.body;
2011
+ } else {
2012
+ mailOptions.text = opts.body;
2013
+ }
2014
+ const info = await transporter.sendMail(mailOptions);
2015
+ return {
2016
+ messageId: info.messageId,
2017
+ accepted: info.accepted ?? [],
2018
+ rejected: info.rejected ?? []
2019
+ };
2020
+ }
2021
+
2022
+ // src/commands/mail/send.ts
2023
+ var mailSendCommand = new import_commander30.Command("send").description("\uBA54\uC77C \uBC1C\uC1A1").requiredOption("--to <addresses...>", "\uBC1B\uB294\uC0AC\uB78C \uC774\uBA54\uC77C (\uBCF5\uC218 \uAC00\uB2A5)").requiredOption("--subject <title>", "\uC81C\uBAA9").option("--body <text>", "\uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--bcc <addresses...>", "\uC228\uC740\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (opts) => {
2024
+ const globalOpts = mailSendCommand.optsWithGlobals();
2025
+ const config = await getConfigOrThrow();
2026
+ let body = opts.body ?? "";
2027
+ if (opts.bodyFile) {
2028
+ body = await (0, import_promises12.readFile)(opts.bodyFile, "utf-8");
2029
+ }
2030
+ if (!body) {
2031
+ process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
2032
+ process.exit(3);
2033
+ }
2034
+ startSpinner("\uBA54\uC77C \uBC1C\uC1A1 \uC911...");
2035
+ const result = await sendMail(config, {
2036
+ to: opts.to,
2037
+ cc: opts.cc,
2038
+ bcc: opts.bcc,
2039
+ subject: opts.subject,
2040
+ body,
2041
+ html: opts.html
2042
+ });
2043
+ stopSpinner(true, "\uBA54\uC77C \uBC1C\uC1A1 \uC644\uB8CC");
2044
+ if (globalOpts.json) {
2045
+ printJson(result);
2046
+ } else {
2047
+ process.stdout.write(
2048
+ `\uBA54\uC77C \uBC1C\uC1A1 \uC644\uB8CC
2049
+ Message-ID: ${result.messageId}
2050
+ \uC218\uC2E0: ${result.accepted.join(", ")}
2051
+ ` + (result.rejected.length ? ` \uAC70\uBD80: ${result.rejected.join(", ")}
2052
+ ` : "")
2053
+ );
2054
+ }
2055
+ });
2056
+
2057
+ // src/commands/mail/reply.ts
2058
+ var import_commander31 = require("commander");
2059
+ var import_promises13 = require("fs/promises");
2060
+ var import_imapflow2 = require("imapflow");
2061
+ async function getMessageId(config, uid) {
2062
+ const imap = getImapConfigOrThrow(config);
2063
+ const client = new import_imapflow2.ImapFlow({
2064
+ host: imap.host,
2065
+ port: imap.port,
2066
+ secure: true,
2067
+ auth: { user: imap.username, pass: imap.password },
2068
+ logger: false
2069
+ });
2070
+ try {
2071
+ await client.connect();
2072
+ const lock = await client.getMailboxLock("INBOX");
2073
+ try {
2074
+ const msg = await client.fetchOne(String(uid), {
2075
+ uid: true,
2076
+ envelope: true
2077
+ }, { uid: true });
2078
+ return msg?.envelope.messageId ?? null;
2079
+ } finally {
2080
+ lock.release();
2081
+ }
2082
+ } finally {
2083
+ await client.logout();
2084
+ }
2085
+ }
2086
+ var mailReplyCommand = new import_commander31.Command("reply").description("\uBA54\uC77C \uB2F5\uC7A5").argument("<uid>", "\uC6D0\uBCF8 \uBA54\uC77C UID").option("--body <text>", "\uB2F5\uC7A5 \uBCF8\uBB38").option("--body-file <path>", "\uB2F5\uC7A5 \uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (uid, opts) => {
2087
+ const globalOpts = mailReplyCommand.optsWithGlobals();
2088
+ const config = await getConfigOrThrow();
2089
+ let body = opts.body ?? "";
2090
+ if (opts.bodyFile) {
2091
+ body = await (0, import_promises13.readFile)(opts.bodyFile, "utf-8");
2092
+ }
2093
+ if (!body) {
2094
+ process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
2095
+ process.exit(3);
2096
+ }
2097
+ startSpinner("\uC6D0\uBCF8 \uBA54\uC77C \uC870\uD68C \uC911...");
2098
+ const original = await getMail(config, Number(uid));
2099
+ const messageId = await getMessageId(config, Number(uid));
2100
+ const fromMatch = original.from.match(/<(.+?)>/);
2101
+ const replyTo = fromMatch ? fromMatch[1] : original.from;
2102
+ stopSpinner(true, "\uC6D0\uBCF8 \uBA54\uC77C \uC870\uD68C \uC644\uB8CC");
2103
+ startSpinner("\uB2F5\uC7A5 \uBC1C\uC1A1 \uC911...");
2104
+ const result = await sendMail(config, {
2105
+ to: [replyTo],
2106
+ cc: opts.cc,
2107
+ subject: original.subject.startsWith("Re: ") ? original.subject : `Re: ${original.subject}`,
2108
+ body,
2109
+ html: opts.html,
2110
+ inReplyTo: messageId ?? void 0,
2111
+ references: messageId ?? void 0
2112
+ });
2113
+ stopSpinner(true, "\uB2F5\uC7A5 \uBC1C\uC1A1 \uC644\uB8CC");
2114
+ if (globalOpts.json) {
2115
+ printJson(result);
2116
+ } else {
2117
+ process.stdout.write(
2118
+ `\uB2F5\uC7A5 \uBC1C\uC1A1 \uC644\uB8CC
2119
+ To: ${replyTo}
2120
+ Message-ID: ${result.messageId}
2121
+ `
2122
+ );
2123
+ }
2124
+ });
2125
+
1558
2126
  // src/index.ts
1559
- var program = new import_commander23.Command();
1560
- program.name("dooray").description("Dooray REST API CLI").version("0.1.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
2127
+ var program = new import_commander32.Command();
2128
+ program.name("dooray").description("Dooray REST API CLI").version("0.3.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
1561
2129
  program.hook("preAction", () => {
1562
2130
  const opts = program.opts();
1563
2131
  if (opts.color === false || process.env.NO_COLOR) {
1564
- import_chalk4.default.level = 0;
2132
+ import_chalk6.default.level = 0;
1565
2133
  }
1566
2134
  });
1567
- var projectCommand = new import_commander23.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
2135
+ var projectCommand = new import_commander32.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
1568
2136
  projectCommand.addCommand(projectListCommand);
1569
2137
  projectCommand.addCommand(projectMembersCommand);
1570
2138
  projectCommand.addCommand(projectWorkflowsCommand);
1571
- var postCommand = new import_commander23.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
2139
+ var postCommand = new import_commander32.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
1572
2140
  postCommand.addCommand(postListCommand);
1573
2141
  postCommand.addCommand(postSearchCommand);
1574
2142
  postCommand.addCommand(postGetCommand);
@@ -1576,31 +2144,44 @@ postCommand.addCommand(postEditCommand);
1576
2144
  postCommand.addCommand(postCreateCommand);
1577
2145
  postCommand.addCommand(postDoneCommand);
1578
2146
  postCommand.addCommand(postWorkflowCommand);
1579
- var commentCommand = new import_commander23.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
2147
+ var commentCommand = new import_commander32.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
1580
2148
  commentCommand.addCommand(commentListCommand);
1581
2149
  commentCommand.addCommand(commentAddCommand);
1582
2150
  commentCommand.addCommand(commentEditCommand);
1583
2151
  commentCommand.addCommand(commentDeleteCommand);
1584
2152
  postCommand.addCommand(commentCommand);
1585
- var wikiCommand = new import_commander23.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
2153
+ var fileCommand = new import_commander32.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
2154
+ fileCommand.addCommand(fileListCommand);
2155
+ fileCommand.addCommand(fileDownloadCommand);
2156
+ fileCommand.addCommand(fileDownloadAllCommand);
2157
+ fileCommand.addCommand(fileUploadCommand);
2158
+ fileCommand.addCommand(fileDeleteCommand);
2159
+ postCommand.addCommand(fileCommand);
2160
+ var wikiCommand = new import_commander32.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
1586
2161
  wikiCommand.addCommand(wikiListCommand);
1587
2162
  wikiCommand.addCommand(wikiPagesCommand);
1588
- var wikiPageCommand = new import_commander23.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
2163
+ var wikiPageCommand = new import_commander32.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
1589
2164
  wikiPageCommand.addCommand(wikiPageGetCommand);
1590
2165
  wikiPageCommand.addCommand(wikiPageCreateCommand);
1591
2166
  wikiPageCommand.addCommand(wikiPageEditCommand);
1592
2167
  wikiCommand.addCommand(wikiPageCommand);
2168
+ var mailCommand = new import_commander32.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
2169
+ mailCommand.addCommand(mailListCommand);
2170
+ mailCommand.addCommand(mailGetCommand);
2171
+ mailCommand.addCommand(mailSendCommand);
2172
+ mailCommand.addCommand(mailReplyCommand);
1593
2173
  program.addCommand(configCommand);
1594
2174
  program.addCommand(cacheCommand);
1595
2175
  program.addCommand(doctorCommand);
1596
2176
  program.addCommand(projectCommand);
1597
2177
  program.addCommand(postCommand);
1598
2178
  program.addCommand(wikiCommand);
2179
+ program.addCommand(mailCommand);
1599
2180
  program.parseAsync().catch((err) => {
1600
2181
  if (err instanceof DoorayCliError) {
1601
- process.stderr.write(import_chalk4.default.red(`\uC624\uB958: ${err.message}`) + "\n");
2182
+ process.stderr.write(import_chalk6.default.red(`\uC624\uB958: ${err.message}`) + "\n");
1602
2183
  process.exit(err.exitCode);
1603
2184
  }
1604
- process.stderr.write(import_chalk4.default.red(`\uC624\uB958: ${err.message}`) + "\n");
2185
+ process.stderr.write(import_chalk6.default.red(`\uC624\uB958: ${err.message}`) + "\n");
1605
2186
  process.exit(1);
1606
2187
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bifos/dooray-cli",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "CLI tool for Dooray project management — AI agent & terminal friendly",
5
5
  "keywords": [
6
6
  "dooray",
@@ -36,14 +36,19 @@
36
36
  "chalk": "^5.6.2",
37
37
  "cli-table3": "^0.6.5",
38
38
  "commander": "^14.0.3",
39
+ "imapflow": "^1.2.18",
39
40
  "js-yaml": "^4.1.1",
40
41
  "ky": "^1.14.3",
42
+ "mailparser": "^3.9.6",
43
+ "nodemailer": "^8.0.4",
41
44
  "ora": "^9.3.0",
42
45
  "tmp": "^0.2.5"
43
46
  },
44
47
  "devDependencies": {
45
48
  "@types/js-yaml": "^4.0.9",
49
+ "@types/mailparser": "^3.4.6",
46
50
  "@types/node": "^25.5.0",
51
+ "@types/nodemailer": "^7.0.11",
47
52
  "@types/tmp": "^0.2.6",
48
53
  "tsup": "^8.5.1",
49
54
  "typescript": "^6.0.2"