@bifos/dooray-cli 0.5.3 → 0.6.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/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_commander36 = require("commander");
27
+ var import_commander46 = require("commander");
28
28
  var import_chalk7 = __toESM(require("chalk"));
29
29
 
30
30
  // src/commands/config.ts
@@ -127,10 +127,13 @@ async function setConfigValue(key, value) {
127
127
  case "smtp-port":
128
128
  config.smtpPort = parseInt(value, 10);
129
129
  break;
130
+ case "track-last-run":
131
+ config.trackLastRun = value === "true";
132
+ break;
130
133
  default:
131
134
  throw new DoorayCliError(
132
135
  `\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${key}
133
- \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url, tenant-name, imap-host, imap-port, imap-username, imap-password, smtp-host, smtp-port`,
136
+ \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url, tenant-name, imap-host, imap-port, imap-username, imap-password, smtp-host, smtp-port, track-last-run`,
134
137
  EXIT_CONFIG_ERROR
135
138
  );
136
139
  }
@@ -200,6 +203,7 @@ var MEMBERS_DIR = (0, import_node_path2.join)(CACHE_DIR, "members");
200
203
  var WORKFLOWS_DIR = (0, import_node_path2.join)(CACHE_DIR, "workflows");
201
204
  var TAGS_DIR = (0, import_node_path2.join)(CACHE_DIR, "tags");
202
205
  var MILESTONES_DIR = (0, import_node_path2.join)(CACHE_DIR, "milestones");
206
+ var MEMBER_GROUPS_DIR = (0, import_node_path2.join)(CACHE_DIR, "member-groups");
203
207
  var WIKIS_PATH = (0, import_node_path2.join)(CACHE_DIR, "wikis.json");
204
208
  async function ensureDir2(dir) {
205
209
  await (0, import_promises2.mkdir)(dir, { recursive: true });
@@ -278,6 +282,15 @@ async function getMilestones(projectId) {
278
282
  async function setMilestones(projectId, items) {
279
283
  await writeJson(milestonesPath(projectId), { updatedAt: now(), data: items });
280
284
  }
285
+ function memberGroupsPath(projectId) {
286
+ return (0, import_node_path2.join)(MEMBER_GROUPS_DIR, `${projectId}.json`);
287
+ }
288
+ async function getMemberGroups(projectId) {
289
+ return readJson(memberGroupsPath(projectId));
290
+ }
291
+ async function setMemberGroups(projectId, items) {
292
+ await writeJson(memberGroupsPath(projectId), { updatedAt: now(), data: items });
293
+ }
281
294
  async function getWikis() {
282
295
  return readJson(WIKIS_PATH);
283
296
  }
@@ -317,8 +330,14 @@ async function getCacheStats() {
317
330
  milestoneProjectCount = files.filter((f) => f.endsWith(".json")).length;
318
331
  } catch {
319
332
  }
333
+ let memberGroupProjectCount = 0;
334
+ try {
335
+ const files = await (0, import_promises2.readdir)(MEMBER_GROUPS_DIR);
336
+ memberGroupProjectCount = files.filter((f) => f.endsWith(".json")).length;
337
+ } catch {
338
+ }
320
339
  const me = await getMe();
321
- return { projectCount, memberProjectCount, workflowProjectCount, tagProjectCount, milestoneProjectCount, me: me?.data ?? null };
340
+ return { projectCount, memberProjectCount, workflowProjectCount, tagProjectCount, milestoneProjectCount, memberGroupProjectCount, me: me?.data ?? null };
322
341
  }
323
342
 
324
343
  // src/commands/cache.ts
@@ -509,6 +528,13 @@ var DoorayApiClient = class {
509
528
  return toDoorayCliError(e);
510
529
  }
511
530
  }
531
+ async getPostComment(projectId, postId, logId) {
532
+ try {
533
+ return await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/logs/${logId}`).json();
534
+ } catch (e) {
535
+ return toDoorayCliError(e);
536
+ }
537
+ }
512
538
  async createPostComment(projectId, postId, body) {
513
539
  try {
514
540
  return await this.api.post(`project/v1/projects/${projectId}/posts/${postId}/logs`, { json: body }).json();
@@ -551,6 +577,23 @@ var DoorayApiClient = class {
551
577
  return toDoorayCliError(e);
552
578
  }
553
579
  }
580
+ async searchMembers(params) {
581
+ try {
582
+ return await this.api.get("common/v1/members", {
583
+ searchParams: {
584
+ ...params.name && { name: params.name },
585
+ ...params.externalEmailAddresses && { externalEmailAddresses: params.externalEmailAddresses },
586
+ ...params.userCode && { userCode: params.userCode },
587
+ ...params.userCodeExact && { userCodeExact: params.userCodeExact },
588
+ ...params.idProviderUserId && { idProviderUserId: params.idProviderUserId },
589
+ ...params.page != null && { page: params.page },
590
+ ...params.size != null && { size: params.size }
591
+ }
592
+ }).json();
593
+ } catch (e) {
594
+ return toDoorayCliError(e);
595
+ }
596
+ }
554
597
  // ─── Workflows ──────────────────────────────────────
555
598
  async getProjectWorkflows(projectId) {
556
599
  try {
@@ -572,6 +615,19 @@ var DoorayApiClient = class {
572
615
  return toDoorayCliError(e);
573
616
  }
574
617
  }
618
+ // ─── Member Groups ──────────────────────────────────
619
+ async getProjectMemberGroups(projectId, params) {
620
+ try {
621
+ return await this.api.get(`project/v1/projects/${projectId}/member-groups`, {
622
+ searchParams: {
623
+ ...params?.page != null && { page: params.page },
624
+ ...params?.size != null && { size: params.size }
625
+ }
626
+ }).json();
627
+ } catch (e) {
628
+ return toDoorayCliError(e);
629
+ }
630
+ }
575
631
  // ─── Milestones ─────────────────────────────────────
576
632
  async getProjectMilestones(projectId, params) {
577
633
  try {
@@ -745,18 +801,30 @@ var ME_TTL_MS = 864e5;
745
801
  var TAGS_TTL_MS = 864e5;
746
802
  var MILESTONES_TTL_MS = 864e5;
747
803
  var RESOLVER_FETCH_PAGE_SIZE = 100;
804
+ var MEMBER_GROUPS_TTL_MS = 864e5;
748
805
  var WIKIS_TTL_MS = 864e5;
749
806
 
750
807
  // src/resolvers/me.ts
751
808
  async function ensureMe(client) {
752
809
  const entry = await getMe();
753
- if (entry && !isExpired(entry.updatedAt, ME_TTL_MS)) {
810
+ if (entry && !isExpired(entry.updatedAt, ME_TTL_MS) && entry.data.orgId) {
754
811
  return entry.data;
755
812
  }
756
813
  const res = await client.getMe();
757
- const me = { id: res.result.id, name: res.result.name };
758
- await setMe(me);
759
- return me;
814
+ const orgId = res.result.defaultOrganization?.id ?? "";
815
+ if (!orgId) {
816
+ throw new DoorayCliError(
817
+ "orgId\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (getMe \uC751\uB2F5\uC5D0 defaultOrganization.id \uB204\uB77D). dooray cache clear \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694.",
818
+ EXIT_PARAM_ERROR
819
+ );
820
+ }
821
+ const cached = {
822
+ id: res.result.id,
823
+ name: res.result.name,
824
+ orgId
825
+ };
826
+ await setMe(cached);
827
+ return cached;
760
828
  }
761
829
 
762
830
  // src/commands/doctor.ts
@@ -787,6 +855,7 @@ var doctorCommand = new import_commander3.Command("doctor").description("\uC124\
787
855
  console.log(` \uC6CC\uD06C\uD50C\uB85C\uC6B0: ${stats.workflowProjectCount}\uAC1C \uD504\uB85C\uC81D\uD2B8`);
788
856
  console.log(` Tag \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.tagProjectCount}`);
789
857
  console.log(` Milestone \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.milestoneProjectCount}`);
858
+ console.log(` Member Group \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.memberGroupProjectCount}`);
790
859
  const claudeDir = import_path.default.join(
791
860
  process.env.HOME ?? process.env.USERPROFILE ?? "",
792
861
  ".claude"
@@ -832,10 +901,10 @@ var import_chalk4 = __toESM(require("chalk"));
832
901
  var import_path2 = __toESM(require("path"));
833
902
  var import_promises5 = __toESM(require("fs/promises"));
834
903
  var setupCommand = new import_commander4.Command("setup").description("\uB300\uD654\uD615 \uCD08\uAE30 \uC124\uC815 \uB9C8\uBC95\uC0AC").action(async () => {
835
- const { input, select, password, confirm } = await import("@inquirer/prompts");
904
+ const { input: input2, select, password, confirm: confirm2 } = await import("@inquirer/prompts");
836
905
  const existing = await getConfig();
837
906
  try {
838
- const tenantName = await input({
907
+ const tenantName = await input2({
839
908
  message: "\uD14C\uB10C\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694",
840
909
  default: existing?.tenantName ?? DEFAULTS.tenantName,
841
910
  theme: { prefix: "\u{1F3E2}" }
@@ -884,7 +953,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
884
953
  );
885
954
  }
886
955
  }
887
- const useMail = await confirm({
956
+ const useMail = await confirm2({
888
957
  message: "\uBA54\uC77C \uAE30\uB2A5\uC744 \uC0AC\uC6A9\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
889
958
  default: !!existing?.imapUsername,
890
959
  theme: { prefix: "\u{1F4E7}" }
@@ -892,7 +961,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
892
961
  let imapUsername;
893
962
  let imapPassword;
894
963
  if (useMail) {
895
- imapUsername = await input({
964
+ imapUsername = await input2({
896
965
  message: `IMAP \uC0AC\uC6A9\uC790 \uC774\uBA54\uC77C (\uD655\uC778: https://${tenantName}.dooray.com/setting/mail/general/read)`,
897
966
  default: existing?.imapUsername,
898
967
  theme: { prefix: "\u{1F4E8}" }
@@ -917,7 +986,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
917
986
  )
918
987
  );
919
988
  } else {
920
- const installSkill = await confirm({
989
+ const installSkill = await confirm2({
921
990
  message: "Claude Code \uC2A4\uD0AC\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
922
991
  default: true,
923
992
  theme: { prefix: "\u{1F527}" }
@@ -942,6 +1011,11 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
942
1011
  }
943
1012
  }
944
1013
  }
1014
+ const trackLastRun = await confirm2({
1015
+ message: "feedback --last \uBAA8\uB4DC \uD65C\uC131\uD654 (\uC9C1\uC804 \uBA85\uB839 \uC5D0\uB7EC\uB97C GitHub issue\uC5D0 \uC790\uB3D9 \uCCA8\uBD80)?",
1016
+ default: false,
1017
+ theme: { prefix: "\u{1F4CB}" }
1018
+ });
945
1019
  const config = {
946
1020
  version: 1,
947
1021
  apiKey,
@@ -954,6 +1028,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
954
1028
  smtpHost: useMail ? DEFAULTS.smtpHost : existing?.smtpHost,
955
1029
  smtpPort: useMail ? DEFAULTS.smtpPort : existing?.smtpPort
956
1030
  };
1031
+ config.trackLastRun = trackLastRun;
957
1032
  await saveConfig(config);
958
1033
  console.log(
959
1034
  import_chalk4.default.green(
@@ -1009,17 +1084,17 @@ async function ensurePrivateProjects(client) {
1009
1084
  await setPrivateProjects(items);
1010
1085
  return items;
1011
1086
  }
1012
- async function resolveProject(client, input) {
1087
+ async function resolveProject(client, input2) {
1013
1088
  const projects = await ensureProjects(client);
1014
- const match = projects.find((p) => p.code === input || p.id === input);
1089
+ const match = projects.find((p) => p.code === input2 || p.id === input2);
1015
1090
  if (match) return match.id;
1016
1091
  const privateCached = await getPrivateProjects();
1017
1092
  if (privateCached && !isExpired(privateCached.updatedAt, PROJECTS_TTL_MS)) {
1018
- const privateMatch = privateCached.data.find((p) => p.code === input || p.id === input);
1093
+ const privateMatch = privateCached.data.find((p) => p.code === input2 || p.id === input2);
1019
1094
  if (privateMatch) return privateMatch.id;
1020
1095
  }
1021
1096
  throw new DoorayCliError(
1022
- `\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}
1097
+ `\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input2}
1023
1098
  \uAC1C\uC778 \uD504\uB85C\uC81D\uD2B8\uB77C\uBA74: dooray project list --type private \uB85C \uCE90\uC2DC\uB97C \uAC31\uC2E0\uD558\uC138\uC694`,
1024
1099
  EXIT_PARAM_ERROR
1025
1100
  );
@@ -1053,17 +1128,29 @@ function output(opts, data) {
1053
1128
  // src/utils/spinner.ts
1054
1129
  var import_ora = __toESM(require("ora"));
1055
1130
  var current = null;
1131
+ var quiet = false;
1132
+ var noopSpinner = new Proxy({}, {
1133
+ get(_target, prop) {
1134
+ if (prop === "text" || prop === "prefixText" || prop === "suffixText") return "";
1135
+ if (prop === "isSpinning") return false;
1136
+ return () => noopSpinner;
1137
+ },
1138
+ set() {
1139
+ return true;
1140
+ }
1141
+ });
1142
+ function setQuiet(value) {
1143
+ quiet = value;
1144
+ }
1056
1145
  function startSpinner(text) {
1146
+ if (quiet) return noopSpinner;
1057
1147
  current = (0, import_ora.default)({ text, stream: process.stderr }).start();
1058
1148
  return current;
1059
1149
  }
1060
1150
  function stopSpinner(success, text) {
1061
1151
  if (!current) return;
1062
- if (success === false) {
1063
- current.fail(text);
1064
- } else {
1065
- current.succeed(text);
1066
- }
1152
+ if (success === false) current.fail(text);
1153
+ else current.succeed(text);
1067
1154
  current = null;
1068
1155
  }
1069
1156
 
@@ -1095,27 +1182,27 @@ var projectListCommand = new import_commander5.Command("list").description("\uD5
1095
1182
  var import_commander6 = require("commander");
1096
1183
 
1097
1184
  // src/resolvers/match.ts
1098
- function matchByName(items, input, label, renderCandidate) {
1099
- const exact = items.filter((i) => i.name === input);
1185
+ function matchByName(items, input2, label, renderCandidate) {
1186
+ const exact = items.filter((i) => i.name === input2);
1100
1187
  if (exact.length === 1) return exact[0];
1101
1188
  if (exact.length > 1) {
1102
1189
  throw new DoorayCliError(
1103
- `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4(\uC815\uD655\uC77C\uCE58): "${input}"
1190
+ `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4(\uC815\uD655\uC77C\uCE58): "${input2}"
1104
1191
  ` + exact.map((i) => ` - ${renderCandidate(i)}`).join("\n"),
1105
1192
  EXIT_PARAM_ERROR
1106
1193
  );
1107
1194
  }
1108
- const partial = items.filter((i) => i.name.includes(input));
1195
+ const partial = items.filter((i) => i.name.includes(input2));
1109
1196
  if (partial.length === 1) return partial[0];
1110
1197
  if (partial.length > 1) {
1111
1198
  throw new DoorayCliError(
1112
- `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4: "${input}"
1199
+ `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4: "${input2}"
1113
1200
  ` + partial.map((i) => ` - ${renderCandidate(i)}`).join("\n"),
1114
1201
  EXIT_PARAM_ERROR
1115
1202
  );
1116
1203
  }
1117
1204
  throw new DoorayCliError(
1118
- `${label}\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1205
+ `${label}\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input2}`,
1119
1206
  EXIT_PARAM_ERROR
1120
1207
  );
1121
1208
  }
@@ -1169,11 +1256,11 @@ async function buildMemberNameMap(client, projectId) {
1169
1256
  }
1170
1257
  return map;
1171
1258
  }
1172
- async function resolveMember(client, projectId, input) {
1259
+ async function resolveMember(client, projectId, input2) {
1173
1260
  const members = await ensureMembers(client, projectId);
1174
1261
  const match = matchByName(
1175
1262
  members,
1176
- input,
1263
+ input2,
1177
1264
  "\uBA64\uBC84",
1178
1265
  (m) => `${m.name} (${m.organizationMemberId})`
1179
1266
  );
@@ -1219,17 +1306,17 @@ async function ensureWorkflows(client, projectId) {
1219
1306
  await setWorkflows(projectId, items);
1220
1307
  return items;
1221
1308
  }
1222
- async function resolveWorkflow(client, projectId, input) {
1309
+ async function resolveWorkflow(client, projectId, input2) {
1223
1310
  const workflows = await ensureWorkflows(client, projectId);
1224
- const byClass = workflows.filter((w) => w.class === input);
1311
+ const byClass = workflows.filter((w) => w.class === input2);
1225
1312
  if (byClass.length === 1) return byClass[0].id;
1226
1313
  if (byClass.length > 1) {
1227
1314
  throw new DoorayCliError(
1228
- `\uB3D9\uC77C class\uC758 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4: "${input}"`,
1315
+ `\uB3D9\uC77C class\uC758 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4: "${input2}"`,
1229
1316
  EXIT_PARAM_ERROR
1230
1317
  );
1231
1318
  }
1232
- const match = matchByName(workflows, input, "\uC6CC\uD06C\uD50C\uB85C\uC6B0", (w) => `${w.name} [${w.class}] (${w.id})`);
1319
+ const match = matchByName(workflows, input2, "\uC6CC\uD06C\uD50C\uB85C\uC6B0", (w) => `${w.name} [${w.class}] (${w.id})`);
1233
1320
  return match.id;
1234
1321
  }
1235
1322
 
@@ -1255,9 +1342,194 @@ var projectWorkflowsCommand = new import_commander7.Command("workflows").descrip
1255
1342
  });
1256
1343
  });
1257
1344
 
1258
- // src/commands/post/list.ts
1345
+ // src/commands/project/groups.ts
1259
1346
  var import_commander8 = require("commander");
1260
1347
 
1348
+ // src/resolvers/member-group.ts
1349
+ async function fetchAllMemberGroups(client, projectId) {
1350
+ const all = [];
1351
+ let page = 0;
1352
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1353
+ while (true) {
1354
+ const res = await client.getProjectMemberGroups(projectId, { page, size });
1355
+ for (const g of res.result) {
1356
+ all.push({ id: g.id, code: g.code });
1357
+ }
1358
+ const total = res.totalCount ?? all.length;
1359
+ if (all.length >= total) break;
1360
+ page++;
1361
+ }
1362
+ return all;
1363
+ }
1364
+ async function ensureMemberGroups(client, projectId) {
1365
+ const entry = await getMemberGroups(projectId);
1366
+ if (entry && !isExpired(entry.updatedAt, MEMBER_GROUPS_TTL_MS)) return entry.data;
1367
+ const items = await fetchAllMemberGroups(client, projectId);
1368
+ await setMemberGroups(projectId, items);
1369
+ return items;
1370
+ }
1371
+ async function resolveMemberGroup(client, projectId, input2) {
1372
+ const groups = await ensureMemberGroups(client, projectId);
1373
+ const adapter = groups.map((g) => ({ name: g.code, id: g.id, code: g.code }));
1374
+ const match = matchByName(adapter, input2, "\uADF8\uB8F9", (g) => `${g.code} (${g.id})`);
1375
+ return { id: match.id, code: match.code };
1376
+ }
1377
+
1378
+ // src/commands/project/groups.ts
1379
+ var projectGroupsCommand = new import_commander8.Command("groups").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
1380
+ const globalOpts = projectGroupsCommand.optsWithGlobals();
1381
+ const config = await getConfigOrThrow();
1382
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1383
+ startSpinner("\uBA64\uBC84 \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C \uC911...");
1384
+ const projectId = await resolveProject(client, project);
1385
+ const groups = await ensureMemberGroups(client, projectId);
1386
+ stopSpinner(true, "\uBA64\uBC84 \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1387
+ output(globalOpts, {
1388
+ headers: ["ID", "Code"],
1389
+ rows: groups.map((g) => [g.id, g.code]),
1390
+ raw: groups,
1391
+ ids: groups.map((g) => g.id)
1392
+ });
1393
+ });
1394
+
1395
+ // src/commands/project/tags.ts
1396
+ var import_commander9 = require("commander");
1397
+
1398
+ // src/resolvers/tag.ts
1399
+ async function fetchAllTags(client, projectId) {
1400
+ const all = [];
1401
+ let page = 0;
1402
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1403
+ while (true) {
1404
+ const res = await client.getProjectTags(projectId, { page, size });
1405
+ for (const t of res.result) {
1406
+ all.push({
1407
+ id: t.id,
1408
+ name: t.name ?? "",
1409
+ color: t.color ?? "",
1410
+ groupId: t.tagGroup?.id ?? null,
1411
+ groupName: t.tagGroup?.name ?? null,
1412
+ groupMandatory: t.tagGroup?.mandatory ?? false,
1413
+ groupSelectOne: t.tagGroup?.selectOne ?? false
1414
+ });
1415
+ }
1416
+ const total = res.totalCount ?? all.length;
1417
+ if (all.length >= total) break;
1418
+ page++;
1419
+ }
1420
+ return all;
1421
+ }
1422
+ async function ensureTags(client, projectId) {
1423
+ const entry = await getTags(projectId);
1424
+ if (entry && !isExpired(entry.updatedAt, TAGS_TTL_MS)) return entry.data;
1425
+ const items = await fetchAllTags(client, projectId);
1426
+ await setTags(projectId, items);
1427
+ return items;
1428
+ }
1429
+ function buildMandatoryHint(allTags, missingGroupIds) {
1430
+ const lines = [];
1431
+ for (const groupId of missingGroupIds) {
1432
+ const groupTags = allTags.filter((t) => t.groupId === groupId);
1433
+ const groupName = groupTags[0]?.groupName ?? groupId;
1434
+ const candidates = groupTags.map((t) => t.name).join(", ");
1435
+ lines.push(` - "${groupName}": ${candidates || "(\uD0DC\uADF8 \uC5C6\uC74C)"}`);
1436
+ }
1437
+ return lines.join("\n");
1438
+ }
1439
+ async function validateMandatoryTags(client, projectId) {
1440
+ const allTags = await ensureTags(client, projectId);
1441
+ const missingGroupIds = [];
1442
+ const seen = /* @__PURE__ */ new Set();
1443
+ for (const t of allTags) {
1444
+ if (t.groupMandatory && t.groupId && !seen.has(t.groupId)) {
1445
+ seen.add(t.groupId);
1446
+ missingGroupIds.push(t.groupId);
1447
+ }
1448
+ }
1449
+ if (missingGroupIds.length === 0) return;
1450
+ throw new DoorayCliError(
1451
+ `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1452
+ ` + buildMandatoryHint(allTags, missingGroupIds) + `
1453
+
1454
+ \uB2E4\uC2DC \uC2DC\uB3C4: --tag "<\uADF8\uB8F9>: <\uD6C4\uBCF4>" \uD615\uC2DD\uC73C\uB85C \uCD94\uAC00`,
1455
+ EXIT_PARAM_ERROR
1456
+ );
1457
+ }
1458
+ async function resolveTags(client, projectId, inputs) {
1459
+ const allTags = await ensureTags(client, projectId);
1460
+ const selected = inputs.map(
1461
+ (input2) => matchByName(
1462
+ allTags,
1463
+ input2,
1464
+ "\uD0DC\uADF8",
1465
+ (t) => t.groupName ? `${t.groupName} / ${t.name} (${t.id})` : `${t.name} (${t.id})`
1466
+ )
1467
+ );
1468
+ const mandatoryGroups = /* @__PURE__ */ new Map();
1469
+ for (const t of allTags) {
1470
+ if (t.groupMandatory && t.groupId) mandatoryGroups.set(t.groupId, t.groupName ?? t.groupId);
1471
+ }
1472
+ const coveredGroups = new Set(selected.map((t) => t.groupId).filter((g) => !!g));
1473
+ const missingIds = [];
1474
+ for (const [gid] of mandatoryGroups) {
1475
+ if (!coveredGroups.has(gid)) missingIds.push(gid);
1476
+ }
1477
+ if (missingIds.length > 0) {
1478
+ throw new DoorayCliError(
1479
+ `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1480
+ ` + buildMandatoryHint(allTags, missingIds),
1481
+ EXIT_PARAM_ERROR
1482
+ );
1483
+ }
1484
+ const selectOneGroups = /* @__PURE__ */ new Map();
1485
+ for (const t of selected) {
1486
+ if (!t.groupSelectOne || !t.groupId) continue;
1487
+ const entry = selectOneGroups.get(t.groupId) ?? { name: t.groupName ?? t.groupId, tags: [] };
1488
+ entry.tags.push(t.name);
1489
+ selectOneGroups.set(t.groupId, entry);
1490
+ }
1491
+ const violators = [];
1492
+ for (const [, info] of selectOneGroups) {
1493
+ if (info.tags.length > 1) {
1494
+ violators.push(`${info.name} (\uC120\uD0DD: ${info.tags.join(", ")})`);
1495
+ }
1496
+ }
1497
+ if (violators.length > 0) {
1498
+ throw new DoorayCliError(
1499
+ `\uB2E4\uC74C \uD0DC\uADF8 \uADF8\uB8F9\uC740 1\uAC1C\uB9CC \uC120\uD0DD \uAC00\uB2A5\uD569\uB2C8\uB2E4:
1500
+ ` + violators.map((v) => ` - ${v}`).join("\n"),
1501
+ EXIT_PARAM_ERROR
1502
+ );
1503
+ }
1504
+ return selected.map((t) => t.id);
1505
+ }
1506
+
1507
+ // src/commands/project/tags.ts
1508
+ var projectTagsCommand = new import_commander9.Command("tags").description("\uD504\uB85C\uC81D\uD2B8 \uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
1509
+ const globalOpts = projectTagsCommand.optsWithGlobals();
1510
+ const config = await getConfigOrThrow();
1511
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1512
+ startSpinner("\uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C \uC911...");
1513
+ const projectId = await resolveProject(client, project);
1514
+ const tags = await ensureTags(client, projectId);
1515
+ stopSpinner(true, "\uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1516
+ output(globalOpts, {
1517
+ headers: ["ID", "Color", "Name", "Group", "Mandatory"],
1518
+ rows: tags.map((t) => [
1519
+ t.id,
1520
+ t.color,
1521
+ t.name,
1522
+ t.groupName ?? "",
1523
+ t.groupMandatory ? "Y" : ""
1524
+ ]),
1525
+ raw: tags,
1526
+ ids: tags.map((t) => t.id)
1527
+ });
1528
+ });
1529
+
1530
+ // src/commands/post/list.ts
1531
+ var import_commander10 = require("commander");
1532
+
1261
1533
  // src/formatters/post.ts
1262
1534
  function formatPostList(posts, opts) {
1263
1535
  output(opts, {
@@ -1307,7 +1579,7 @@ function formatCommentList(comments, opts) {
1307
1579
  }
1308
1580
 
1309
1581
  // src/commands/post/list.ts
1310
- var postListCommand = new import_commander8.Command("list").description("\uC5C5\uBB34 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--subject <keyword>", "\uC81C\uBAA9 \uD0A4\uC6CC\uB4DC \uD544\uD130\uB9C1").option("--all", "\uC804\uCCB4 \uD398\uC774\uC9C0\uB124\uC774\uC158 (\uBAA8\uB4E0 \uACB0\uACFC \uC870\uD68C)").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (project, opts) => {
1582
+ var postListCommand = new import_commander10.Command("list").description("\uC5C5\uBB34 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--subject <keyword>", "\uC81C\uBAA9 \uD0A4\uC6CC\uB4DC \uD544\uD130\uB9C1").option("--all", "\uC804\uCCB4 \uD398\uC774\uC9C0\uB124\uC774\uC158 (\uBAA8\uB4E0 \uACB0\uACFC \uC870\uD68C)").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (project, opts) => {
1311
1583
  const globalOpts = postListCommand.optsWithGlobals();
1312
1584
  const config = await getConfigOrThrow();
1313
1585
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1341,8 +1613,8 @@ var postListCommand = new import_commander8.Command("list").description("\uC5C5\
1341
1613
  });
1342
1614
 
1343
1615
  // src/commands/post/search.ts
1344
- var import_commander9 = require("commander");
1345
- var postSearchCommand = new import_commander9.Command("search").description("\uC5C5\uBB34 \uAC80\uC0C9 (\uC81C\uBAA9 \uD0A4\uC6CC\uB4DC)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<keyword>", "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC").action(async (project, keyword) => {
1616
+ var import_commander11 = require("commander");
1617
+ var postSearchCommand = new import_commander11.Command("search").description("\uC5C5\uBB34 \uAC80\uC0C9 (\uC81C\uBAA9 \uD0A4\uC6CC\uB4DC)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<keyword>", "\uAC80\uC0C9 \uD0A4\uC6CC\uB4DC").action(async (project, keyword) => {
1346
1618
  const globalOpts = postSearchCommand.optsWithGlobals();
1347
1619
  const config = await getConfigOrThrow();
1348
1620
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1354,7 +1626,7 @@ var postSearchCommand = new import_commander9.Command("search").description("\uC
1354
1626
  });
1355
1627
 
1356
1628
  // src/commands/post/get.ts
1357
- var import_commander10 = require("commander");
1629
+ var import_commander12 = require("commander");
1358
1630
 
1359
1631
  // src/resolvers/post.ts
1360
1632
  async function resolvePost(client, projectId, postNumber) {
@@ -1372,12 +1644,16 @@ async function resolvePost(client, projectId, postNumber) {
1372
1644
 
1373
1645
  // src/utils/dooray-url.ts
1374
1646
  var TASK_URL_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/to\/(\d+)(?:[/?#].*)?$/;
1375
- function parseDoorayTaskUrl(input) {
1376
- const m = TASK_URL_RE.exec(input);
1377
- return m ? m[1] : null;
1647
+ var TASK_URL_ALT_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/(?:\d+)\/(\d+)(?:[/?#].*)?$/;
1648
+ function parseDoorayTaskUrl(input2) {
1649
+ const m1 = TASK_URL_RE.exec(input2);
1650
+ if (m1) return m1[1];
1651
+ const m2 = TASK_URL_ALT_RE.exec(input2);
1652
+ if (m2) return m2[1];
1653
+ return null;
1378
1654
  }
1379
- function isLikelyDoorayUrl(input) {
1380
- return /^https?:\/\//.test(input);
1655
+ function isLikelyDoorayUrl(input2) {
1656
+ return /^https?:\/\//.test(input2);
1381
1657
  }
1382
1658
 
1383
1659
  // src/resolvers/post-input.ts
@@ -1448,7 +1724,7 @@ async function resolvePostInput(client, args) {
1448
1724
  }
1449
1725
 
1450
1726
  // src/commands/post/get.ts
1451
- var postGetCommand = new import_commander10.Command("get").description("\uC5C5\uBB34 \uC0C1\uC138 \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
1727
+ var postGetCommand = new import_commander12.Command("get").description("\uC5C5\uBB34 \uC0C1\uC138 \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
1452
1728
  const globalOpts = postGetCommand.optsWithGlobals();
1453
1729
  const config = await getConfigOrThrow();
1454
1730
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1465,7 +1741,7 @@ var postGetCommand = new import_commander10.Command("get").description("\uC5C5\u
1465
1741
  });
1466
1742
 
1467
1743
  // src/commands/post/edit.ts
1468
- var import_commander11 = require("commander");
1744
+ var import_commander13 = require("commander");
1469
1745
 
1470
1746
  // src/editor/index.ts
1471
1747
  var import_node_child_process = require("child_process");
@@ -1473,8 +1749,8 @@ var import_promises6 = __toESM(require("fs/promises"));
1473
1749
  var import_tmp = __toESM(require("tmp"));
1474
1750
  var import_js_yaml = __toESM(require("js-yaml"));
1475
1751
  function openInEditor(content) {
1476
- const editor = process.env.EDITOR;
1477
- if (!editor) {
1752
+ const editor2 = process.env.EDITOR;
1753
+ if (!editor2) {
1478
1754
  throw new DoorayCliError(
1479
1755
  "$EDITOR \uD658\uACBD\uBCC0\uC218\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. export EDITOR=vim \uB4F1\uC73C\uB85C \uC124\uC815\uD574\uC8FC\uC138\uC694.",
1480
1756
  EXIT_PARAM_ERROR
@@ -1484,7 +1760,7 @@ function openInEditor(content) {
1484
1760
  return new Promise(async (resolve, reject) => {
1485
1761
  try {
1486
1762
  await import_promises6.default.writeFile(tmpFile.name, content, "utf-8");
1487
- const child = (0, import_node_child_process.spawn)(editor, [tmpFile.name], {
1763
+ const child = (0, import_node_child_process.spawn)(editor2, [tmpFile.name], {
1488
1764
  stdio: "inherit"
1489
1765
  });
1490
1766
  child.on("error", (err) => {
@@ -1635,7 +1911,7 @@ async function resolveUsers(client, projectId, emails) {
1635
1911
  }
1636
1912
  return users;
1637
1913
  }
1638
- var postEditCommand = new import_commander11.Command("edit").description("\uC5C5\uBB34 \uC218\uC815 ($EDITOR \uB610\uB294 --title/--body \uC635\uC158)").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--title <title>", "\uC81C\uBAA9 \uBCC0\uACBD (non-interactive)").option("--subject <subject>", "--title\uC758 deprecated alias").option("--body <text>", "\uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").action(async (project, postNumberStr, opts) => {
1914
+ var postEditCommand = new import_commander13.Command("edit").description("\uC5C5\uBB34 \uC218\uC815 ($EDITOR \uB610\uB294 --title/--body \uC635\uC158)").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--title <title>", "\uC81C\uBAA9 \uBCC0\uACBD (non-interactive)").option("--subject <subject>", "--title\uC758 deprecated alias").option("--body <text>", "\uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").action(async (project, postNumberStr, opts) => {
1639
1915
  const config = await getConfigOrThrow();
1640
1916
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1641
1917
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
@@ -1709,86 +1985,7 @@ var postEditCommand = new import_commander11.Command("edit").description("\uC5C5
1709
1985
  });
1710
1986
 
1711
1987
  // src/commands/post/create.ts
1712
- var import_commander12 = require("commander");
1713
-
1714
- // src/resolvers/tag.ts
1715
- async function fetchAllTags(client, projectId) {
1716
- const all = [];
1717
- let page = 0;
1718
- const size = RESOLVER_FETCH_PAGE_SIZE;
1719
- while (true) {
1720
- const res = await client.getProjectTags(projectId, { page, size });
1721
- for (const t of res.result) {
1722
- all.push({
1723
- id: t.id,
1724
- name: t.name ?? "",
1725
- groupId: t.tagGroup?.id ?? null,
1726
- groupName: t.tagGroup?.name ?? null,
1727
- groupMandatory: t.tagGroup?.mandatory ?? false,
1728
- groupSelectOne: t.tagGroup?.selectOne ?? false
1729
- });
1730
- }
1731
- const total = res.totalCount ?? all.length;
1732
- if (all.length >= total) break;
1733
- page++;
1734
- }
1735
- return all;
1736
- }
1737
- async function ensureTags(client, projectId) {
1738
- const entry = await getTags(projectId);
1739
- if (entry && !isExpired(entry.updatedAt, TAGS_TTL_MS)) return entry.data;
1740
- const items = await fetchAllTags(client, projectId);
1741
- await setTags(projectId, items);
1742
- return items;
1743
- }
1744
- async function resolveTags(client, projectId, inputs) {
1745
- const allTags = await ensureTags(client, projectId);
1746
- const selected = inputs.map(
1747
- (input) => matchByName(
1748
- allTags,
1749
- input,
1750
- "\uD0DC\uADF8",
1751
- (t) => t.groupName ? `${t.groupName} / ${t.name} (${t.id})` : `${t.name} (${t.id})`
1752
- )
1753
- );
1754
- const mandatoryGroups = /* @__PURE__ */ new Map();
1755
- for (const t of allTags) {
1756
- if (t.groupMandatory && t.groupId) mandatoryGroups.set(t.groupId, t.groupName ?? t.groupId);
1757
- }
1758
- const coveredGroups = new Set(selected.map((t) => t.groupId).filter((g) => !!g));
1759
- const missing = [];
1760
- for (const [gid, gname] of mandatoryGroups) {
1761
- if (!coveredGroups.has(gid)) missing.push(gname);
1762
- }
1763
- if (missing.length > 0) {
1764
- throw new DoorayCliError(
1765
- `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1766
- ` + missing.map((g) => ` - ${g}`).join("\n"),
1767
- EXIT_PARAM_ERROR
1768
- );
1769
- }
1770
- const selectOneGroups = /* @__PURE__ */ new Map();
1771
- for (const t of selected) {
1772
- if (!t.groupSelectOne || !t.groupId) continue;
1773
- const entry = selectOneGroups.get(t.groupId) ?? { name: t.groupName ?? t.groupId, tags: [] };
1774
- entry.tags.push(t.name);
1775
- selectOneGroups.set(t.groupId, entry);
1776
- }
1777
- const violators = [];
1778
- for (const [, info] of selectOneGroups) {
1779
- if (info.tags.length > 1) {
1780
- violators.push(`${info.name} (\uC120\uD0DD: ${info.tags.join(", ")})`);
1781
- }
1782
- }
1783
- if (violators.length > 0) {
1784
- throw new DoorayCliError(
1785
- `\uB2E4\uC74C \uD0DC\uADF8 \uADF8\uB8F9\uC740 1\uAC1C\uB9CC \uC120\uD0DD \uAC00\uB2A5\uD569\uB2C8\uB2E4:
1786
- ` + violators.map((v) => ` - ${v}`).join("\n"),
1787
- EXIT_PARAM_ERROR
1788
- );
1789
- }
1790
- return selected.map((t) => t.id);
1791
- }
1988
+ var import_commander14 = require("commander");
1792
1989
 
1793
1990
  // src/resolvers/milestone.ts
1794
1991
  async function fetchAllMilestones(client, projectId) {
@@ -1811,9 +2008,9 @@ async function ensureMilestones(client, projectId) {
1811
2008
  await setMilestones(projectId, items);
1812
2009
  return items;
1813
2010
  }
1814
- async function resolveMilestone(client, projectId, input) {
2011
+ async function resolveMilestone(client, projectId, input2) {
1815
2012
  const all = await ensureMilestones(client, projectId);
1816
- const match = matchByName(all, input, "\uB9C8\uC77C\uC2A4\uD1A4", (m) => `${m.name} (${m.id})`);
2013
+ const match = matchByName(all, input2, "\uB9C8\uC77C\uC2A4\uD1A4", (m) => `${m.name} (${m.id})`);
1817
2014
  return match.id;
1818
2015
  }
1819
2016
 
@@ -1837,13 +2034,13 @@ async function resolvePostRef(client, ref) {
1837
2034
  // src/commands/post/create.ts
1838
2035
  async function resolveUsers2(client, projectId, inputs) {
1839
2036
  const users = [];
1840
- for (const input of inputs) {
1841
- const memberId = await resolveMember(client, projectId, input);
2037
+ for (const input2 of inputs) {
2038
+ const memberId = await resolveMember(client, projectId, input2);
1842
2039
  users.push({ type: "member", member: { organizationMemberId: memberId } });
1843
2040
  }
1844
2041
  return users;
1845
2042
  }
1846
- var postCreateCommand = new import_commander12.Command("create").description("\uC5C5\uBB34 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--title <title>", "\uC5C5\uBB34 \uC81C\uBAA9").option("--subject <subject>", "--title\uC758 deprecated alias").option("--to <members...>", "\uB2F4\uB2F9\uC790 (\uC774\uB984 \uB610\uB294 \uC774\uBA54\uC77C, \uC5EC\uB7EC \uBA85 \uAC00\uB2A5)").option("--cc <members...>", "\uCC38\uC870\uC790 (\uC774\uB984 \uB610\uB294 \uC774\uBA54\uC77C, \uC5EC\uB7EC \uBA85 \uAC00\uB2A5)").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)").option("--priority <level>", "\uC6B0\uC120\uC21C\uC704 (highest, high, normal, low, lowest)", "normal").option("--due-date <date>", "\uB9C8\uAC10\uC77C (ISO 8601 \uD615\uC2DD)").option("--tag <name>", "\uD0DC\uADF8 \uC774\uB984 (\uBC18\uBCF5 \uAC00\uB2A5)", (value, prev) => [...prev, value], []).option("--parent <ref>", "\uBD80\uBAA8 \uC5C5\uBB34 (project/number \uB610\uB294 postId)").option("--workflow <name>", "\uCD08\uAE30 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 class").option("--milestone <name>", "\uB9C8\uC77C\uC2A4\uD1A4 \uC774\uB984").action(async (project, opts) => {
2043
+ var postCreateCommand = new import_commander14.Command("create").description("\uC5C5\uBB34 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--title <title>", "\uC5C5\uBB34 \uC81C\uBAA9").option("--subject <subject>", "--title\uC758 deprecated alias").option("--to <members...>", "\uB2F4\uB2F9\uC790 (\uC774\uB984 \uB610\uB294 \uC774\uBA54\uC77C, \uC5EC\uB7EC \uBA85 \uAC00\uB2A5)").option("--cc <members...>", "\uCC38\uC870\uC790 (\uC774\uB984 \uB610\uB294 \uC774\uBA54\uC77C, \uC5EC\uB7EC \uBA85 \uAC00\uB2A5)").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)").option("--priority <level>", "\uC6B0\uC120\uC21C\uC704 (highest, high, normal, low, lowest)", "normal").option("--due-date <date>", "\uB9C8\uAC10\uC77C (ISO 8601 \uD615\uC2DD)").option("--tag <name>", "\uD0DC\uADF8 \uC774\uB984 (\uBC18\uBCF5 \uAC00\uB2A5)", (value, prev) => [...prev, value], []).option("--parent <ref>", "\uBD80\uBAA8 \uC5C5\uBB34 (project/number \uB610\uB294 postId)").option("--workflow <name>", "\uCD08\uAE30 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 class").option("--milestone <name>", "\uB9C8\uC77C\uC2A4\uD1A4 \uC774\uB984").action(async (project, opts) => {
1847
2044
  const globalOpts = postCreateCommand.optsWithGlobals();
1848
2045
  const config = await getConfigOrThrow();
1849
2046
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1866,7 +2063,7 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1866
2063
  const ccUsers = opts.cc ? await resolveUsers2(client, projectId, opts.cc) : [];
1867
2064
  const tagInputs = (opts.tag ?? []).filter((s) => s.length > 0);
1868
2065
  const [tagIds, parentPostId, milestoneId] = await Promise.all([
1869
- tagInputs.length > 0 ? resolveTags(client, projectId, tagInputs) : Promise.resolve(void 0),
2066
+ tagInputs.length > 0 ? resolveTags(client, projectId, tagInputs) : validateMandatoryTags(client, projectId).then(() => void 0),
1870
2067
  opts.parent ? resolvePostRef(client, opts.parent) : Promise.resolve(void 0),
1871
2068
  opts.milestone ? resolveMilestone(client, projectId, opts.milestone) : Promise.resolve(void 0)
1872
2069
  ]);
@@ -1902,8 +2099,8 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1902
2099
  });
1903
2100
 
1904
2101
  // src/commands/post/done.ts
1905
- var import_commander13 = require("commander");
1906
- var postDoneCommand = new import_commander13.Command("done").description("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
2102
+ var import_commander15 = require("commander");
2103
+ var postDoneCommand = new import_commander15.Command("done").description("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
1907
2104
  const config = await getConfigOrThrow();
1908
2105
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1909
2106
  startSpinner("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC \uC911...");
@@ -1920,8 +2117,8 @@ var postDoneCommand = new import_commander13.Command("done").description("\uC5C5
1920
2117
  });
1921
2118
 
1922
2119
  // src/commands/post/workflow.ts
1923
- var import_commander14 = require("commander");
1924
- var postWorkflowCommand = new import_commander14.Command("workflow").description("\uC5C5\uBB34 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").argument("[workflow]", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 \uD074\uB798\uC2A4 (\uB610\uB294 --workflow \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--workflow <name>", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 (positional \uB300\uCCB4)").action(async (project, postNumber, workflowArg, opts) => {
2120
+ var import_commander16 = require("commander");
2121
+ var postWorkflowCommand = new import_commander16.Command("workflow").description("\uC5C5\uBB34 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").argument("[workflow]", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 \uD074\uB798\uC2A4 (\uB610\uB294 --workflow \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--workflow <name>", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 (positional \uB300\uCCB4)").action(async (project, postNumber, workflowArg, opts) => {
1925
2122
  const config = await getConfigOrThrow();
1926
2123
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1927
2124
  let workflowInput = opts.workflow;
@@ -1961,7 +2158,7 @@ var postWorkflowCommand = new import_commander14.Command("workflow").description
1961
2158
  });
1962
2159
 
1963
2160
  // src/commands/post/comment/list.ts
1964
- var import_commander15 = require("commander");
2161
+ var import_commander17 = require("commander");
1965
2162
 
1966
2163
  // src/utils/comment-enrich.ts
1967
2164
  function enrichCommentCreators(comments, nameMap) {
@@ -1982,23 +2179,150 @@ function enrichCommentCreators(comments, nameMap) {
1982
2179
  }
1983
2180
 
1984
2181
  // src/commands/post/comment/list.ts
1985
- var commentListCommand = new import_commander15.Command("list").description("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (project, postNumberStr, opts) => {
1986
- const globalOpts = commentListCommand.optsWithGlobals();
1987
- const config = await getConfigOrThrow();
1988
- const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1989
- startSpinner("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC911...");
1990
- const { projectId, postId } = await resolvePostInput(client, {
1991
- projectArg: project,
1992
- postNumberArg: postNumberStr,
1993
- idOpt: opts.id,
1994
- urlOpt: opts.url
2182
+ var PAGE_DEFAULT = "0";
2183
+ var SIZE_DEFAULT = "20";
2184
+ function parseSinceDate(input2) {
2185
+ const d = new Date(input2);
2186
+ if (isNaN(d.getTime())) {
2187
+ throw new DoorayCliError(
2188
+ `--since \uAC12\uC744 \uD30C\uC2F1\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: "${input2}" (ISO 8601 \uB610\uB294 YYYY-MM-DD)`,
2189
+ EXIT_PARAM_ERROR
2190
+ );
2191
+ }
2192
+ return d;
2193
+ }
2194
+ function validateExclusive(opts) {
2195
+ function err(msg) {
2196
+ return new DoorayCliError(msg, EXIT_PARAM_ERROR);
2197
+ }
2198
+ if (opts.latest) {
2199
+ if (opts.page && opts.page !== PAGE_DEFAULT) throw err("--latest\uC640 --page\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2200
+ if (opts.size && opts.size !== SIZE_DEFAULT) throw err("--latest\uC640 --size\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2201
+ if (opts.sort && opts.sort !== "asc") throw err("--latest\uC640 --sort\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2202
+ if (opts.reverse) throw err("--latest\uC640 --reverse\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2203
+ if (opts.since) throw err("--latest\uC640 --since\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2204
+ }
2205
+ if (opts.reverse && opts.sort && opts.sort !== "asc") {
2206
+ throw err("--reverse\uC640 --sort \uC635\uC158\uC740 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2207
+ }
2208
+ if (opts.sort && opts.sort !== "asc" && opts.sort !== "desc") {
2209
+ throw err(`--sort\uB294 asc \uB610\uB294 desc\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4: "${opts.sort}"`);
2210
+ }
2211
+ }
2212
+ function resolveOrder(opts) {
2213
+ if (opts.reverse) return "-createdAt";
2214
+ if (opts.sort === "desc") return "-createdAt";
2215
+ return "createdAt";
2216
+ }
2217
+ async function fetchSince(client, projectId, postId, sinceDate) {
2218
+ const sinceMs = sinceDate.getTime();
2219
+ const collected = [];
2220
+ let page = 0;
2221
+ const size = 100;
2222
+ while (true) {
2223
+ const res = await client.getPostComments(projectId, postId, {
2224
+ page,
2225
+ size,
2226
+ order: "-createdAt"
2227
+ });
2228
+ const pageItems = res.result;
2229
+ const survivors = pageItems.filter((c) => new Date(c.createdAt).getTime() >= sinceMs);
2230
+ collected.push(...survivors);
2231
+ const hasOlder = pageItems.length > survivors.length;
2232
+ if (hasOlder) break;
2233
+ if (pageItems.length < size) break;
2234
+ page++;
2235
+ }
2236
+ return collected;
2237
+ }
2238
+ var commentListCommand = new import_commander17.Command("list").description("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", PAGE_DEFAULT).option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", SIZE_DEFAULT).option("--sort <order>", "\uC815\uB82C (asc/desc), \uAE30\uBCF8 asc", "asc").option("--reverse", "--sort desc \uC758 alias").option("--latest <n>", "\uCD5C\uC2E0 N\uAC1C (--sort desc + size=N + page=0 \uB2E8\uCD95, \uCD5C\uB300 100)").option("--since <iso>", "\uC774 \uC2DC\uAC04 \uC774\uD6C4 \uB313\uAE00\uB9CC (ISO 8601 \uB610\uB294 YYYY-MM-DD)").option("--from-author <name>", "\uC791\uC131\uC790 \uC774\uB984\uC73C\uB85C \uD544\uD130 (\uBD80\uBD84\uC77C\uCE58)").action(async (project, postNumberStr, opts) => {
2239
+ const globalOpts = commentListCommand.optsWithGlobals();
2240
+ const config = await getConfigOrThrow();
2241
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2242
+ validateExclusive(opts);
2243
+ if (opts.latest) {
2244
+ const n = Number(opts.latest);
2245
+ if (!Number.isFinite(n) || n <= 0) {
2246
+ throw new DoorayCliError("--latest\uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2247
+ }
2248
+ if (n > 100) {
2249
+ throw new DoorayCliError("--latest\uB294 \uCD5C\uB300 100\uAE4C\uC9C0 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2250
+ }
2251
+ }
2252
+ const sinceDate = opts.since ? parseSinceDate(opts.since) : null;
2253
+ startSpinner("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC911...");
2254
+ const { projectId, postId } = await resolvePostInput(client, {
2255
+ projectArg: project,
2256
+ postNumberArg: postNumberStr,
2257
+ idOpt: opts.id,
2258
+ urlOpt: opts.url
2259
+ });
2260
+ const order = resolveOrder(opts);
2261
+ let comments;
2262
+ if (opts.latest) {
2263
+ const n = Number(opts.latest);
2264
+ const res = await client.getPostComments(projectId, postId, {
2265
+ page: 0,
2266
+ size: n,
2267
+ order: "-createdAt"
2268
+ });
2269
+ comments = res.result;
2270
+ } else if (sinceDate) {
2271
+ comments = await fetchSince(client, projectId, postId, sinceDate);
2272
+ if (order === "createdAt") comments = [...comments].reverse();
2273
+ } else {
2274
+ const res = await client.getPostComments(projectId, postId, {
2275
+ page: Number(opts.page),
2276
+ size: Number(opts.size),
2277
+ order
2278
+ });
2279
+ comments = res.result;
2280
+ }
2281
+ stopSpinner(true, "\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
2282
+ if (opts.fromAuthor) {
2283
+ const memberId = await resolveMember(client, projectId, opts.fromAuthor);
2284
+ comments = comments.filter(
2285
+ (c) => c.creator?.member?.organizationMemberId === memberId
2286
+ );
2287
+ }
2288
+ if (!globalOpts.json) {
2289
+ let nameMap = /* @__PURE__ */ new Map();
2290
+ try {
2291
+ nameMap = await buildMemberNameMap(client, projectId);
2292
+ } catch {
2293
+ }
2294
+ comments = enrichCommentCreators(comments, nameMap);
2295
+ }
2296
+ formatCommentList(comments, globalOpts);
2297
+ });
2298
+
2299
+ // src/commands/post/comment/latest.ts
2300
+ var import_commander18 = require("commander");
2301
+ var commentLatestCommand = new import_commander18.Command("latest").description("\uCD5C\uC2E0 \uB313\uAE00 1\uAC1C \uC870\uD68C (= comment list --latest 1)").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("-n, --count <n>", "\uCD5C\uC2E0 N\uAC1C (\uAE30\uBCF8 1)", "1").action(async (project, postNumberStr, opts) => {
2302
+ const globalOpts = commentLatestCommand.optsWithGlobals();
2303
+ const config = await getConfigOrThrow();
2304
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2305
+ const n = Number(opts.count);
2306
+ if (!Number.isFinite(n) || n <= 0) {
2307
+ throw new DoorayCliError("--count\uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2308
+ }
2309
+ if (n > 100) {
2310
+ throw new DoorayCliError("--count\uB294 \uCD5C\uB300 100\uAE4C\uC9C0 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2311
+ }
2312
+ startSpinner("\uCD5C\uC2E0 \uB313\uAE00 \uC870\uD68C \uC911...");
2313
+ const { projectId, postId } = await resolvePostInput(client, {
2314
+ projectArg: project,
2315
+ postNumberArg: postNumberStr,
2316
+ idOpt: opts.id,
2317
+ urlOpt: opts.url
1995
2318
  });
1996
2319
  const res = await client.getPostComments(projectId, postId, {
1997
- page: Number(opts.page),
1998
- size: Number(opts.size)
2320
+ page: 0,
2321
+ size: Math.min(n, 100),
2322
+ order: "-createdAt"
1999
2323
  });
2000
- stopSpinner(true, "\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
2001
- let comments = res.result;
2324
+ stopSpinner(true, "\uC870\uD68C \uC644\uB8CC");
2325
+ let comments = res.result.slice(0, n);
2002
2326
  if (!globalOpts.json) {
2003
2327
  let nameMap = /* @__PURE__ */ new Map();
2004
2328
  try {
@@ -2011,8 +2335,36 @@ var commentListCommand = new import_commander15.Command("list").description("\uB
2011
2335
  });
2012
2336
 
2013
2337
  // src/commands/post/comment/add.ts
2014
- var import_commander16 = require("commander");
2015
- var commentAddCommand = new import_commander16.Command("add").description("\uB313\uAE00 \uCD94\uAC00").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 (- \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, postNumberStr, opts) => {
2338
+ var import_commander19 = require("commander");
2339
+
2340
+ // src/utils/mention.ts
2341
+ function buildMemberMention(m, me) {
2342
+ const title = m.memberId === me.id ? "me" : "member";
2343
+ return `[@${m.name}](dooray://${me.orgId}/members/${m.memberId} "${title}")`;
2344
+ }
2345
+ function buildGroupMention(g, me) {
2346
+ return `[@${g.projectCode}/${g.code}](dooray://${me.orgId}/member-groups/${g.groupId})`;
2347
+ }
2348
+ function prependMentions(body, members, groups, me) {
2349
+ const parts = [];
2350
+ for (const m of members) parts.push(buildMemberMention(m, me));
2351
+ for (const g of groups) parts.push(buildGroupMention(g, me));
2352
+ if (parts.length === 0) return body;
2353
+ return parts.join(" ") + " " + body;
2354
+ }
2355
+
2356
+ // src/commands/post/comment/add.ts
2357
+ var commentAddCommand = new import_commander19.Command("add").description("\uB313\uAE00 \uCD94\uAC00").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 (- \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)").option(
2358
+ "--mention <name>",
2359
+ "\uBA64\uBC84 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, \uC774\uB984 \uBD80\uBD84\uC77C\uCE58)",
2360
+ (value, prev) => [...prev, value],
2361
+ []
2362
+ ).option(
2363
+ "--mention-group <code>",
2364
+ "\uADF8\uB8F9 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, code \uBD80\uBD84\uC77C\uCE58)",
2365
+ (value, prev) => [...prev, value],
2366
+ []
2367
+ ).action(async (project, postNumberStr, opts) => {
2016
2368
  const globalOpts = commentAddCommand.optsWithGlobals();
2017
2369
  const config = await getConfigOrThrow();
2018
2370
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2025,12 +2377,32 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
2025
2377
  }
2026
2378
  }
2027
2379
  startSpinner("\uB313\uAE00 \uCD94\uAC00 \uC911...");
2028
- const { projectId, postId } = await resolvePostInput(client, {
2380
+ const { projectId, postId, projectCode } = await resolvePostInput(client, {
2029
2381
  projectArg: project,
2030
2382
  postNumberArg: postNumberStr,
2031
2383
  idOpt: opts.id,
2032
2384
  urlOpt: opts.url
2033
2385
  });
2386
+ const mentionInputs = (opts.mention ?? []).filter((s) => s.length > 0);
2387
+ const groupInputs = (opts.mentionGroup ?? []).filter((s) => s.length > 0);
2388
+ if (mentionInputs.length > 0 || groupInputs.length > 0) {
2389
+ const me = await ensureMe(client);
2390
+ const memberIds = await Promise.all(
2391
+ mentionInputs.map((name) => resolveMember(client, projectId, name))
2392
+ );
2393
+ const nameMap = await buildMemberNameMap(client, projectId);
2394
+ const members = memberIds.map((memberId) => ({
2395
+ memberId,
2396
+ name: nameMap.get(memberId) ?? memberId
2397
+ }));
2398
+ const groups = await Promise.all(
2399
+ groupInputs.map(async (code) => {
2400
+ const g = await resolveMemberGroup(client, projectId, code);
2401
+ return { groupId: g.id, code: g.code, projectCode };
2402
+ })
2403
+ );
2404
+ bodyContent = prependMentions(bodyContent, members, groups, me);
2405
+ }
2034
2406
  const res = await client.createPostComment(projectId, postId, {
2035
2407
  body: { mimeType: "text/x-markdown", content: bodyContent }
2036
2408
  });
@@ -2046,8 +2418,18 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
2046
2418
  });
2047
2419
 
2048
2420
  // src/commands/post/comment/edit.ts
2049
- var import_commander17 = require("commander");
2050
- var commentEditCommand = new import_commander17.Command("edit").description("\uB313\uAE00 \uC218\uC815 ($EDITOR \uB610\uB294 --body \uC635\uC158)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").action(async (arg1, arg2, arg3, opts) => {
2421
+ var import_commander20 = require("commander");
2422
+ var commentEditCommand = new import_commander20.Command("edit").description("\uB313\uAE00 \uC218\uC815 ($EDITOR \uB610\uB294 --body \uC635\uC158)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").option(
2423
+ "--mention <name>",
2424
+ "\uBA64\uBC84 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, \uC774\uB984 \uBD80\uBD84\uC77C\uCE58)",
2425
+ (value, prev) => [...prev, value],
2426
+ []
2427
+ ).option(
2428
+ "--mention-group <code>",
2429
+ "\uADF8\uB8F9 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, code \uBD80\uBD84\uC77C\uCE58)",
2430
+ (value, prev) => [...prev, value],
2431
+ []
2432
+ ).action(async (arg1, arg2, arg3, opts) => {
2051
2433
  const config = await getConfigOrThrow();
2052
2434
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2053
2435
  let projectArg;
@@ -2090,7 +2472,7 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
2090
2472
  );
2091
2473
  }
2092
2474
  startSpinner("\uB313\uAE00 \uC870\uD68C \uC911...");
2093
- const { projectId, postId } = await resolvePostInput(client, {
2475
+ const { projectId, postId, projectCode } = await resolvePostInput(client, {
2094
2476
  projectArg,
2095
2477
  postNumberArg,
2096
2478
  idOpt: opts.id,
@@ -2104,14 +2486,38 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
2104
2486
  `);
2105
2487
  process.exit(1);
2106
2488
  }
2489
+ const mentionInputs = (opts.mention ?? []).filter((s) => s.length > 0);
2490
+ const groupInputs = (opts.mentionGroup ?? []).filter((s) => s.length > 0);
2491
+ let mentionPrefix = "";
2492
+ if (mentionInputs.length > 0 || groupInputs.length > 0) {
2493
+ const me = await ensureMe(client);
2494
+ const memberIds = await Promise.all(
2495
+ mentionInputs.map((name) => resolveMember(client, projectId, name))
2496
+ );
2497
+ const nameMap = await buildMemberNameMap(client, projectId);
2498
+ const members = memberIds.map((memberId) => ({
2499
+ memberId,
2500
+ name: nameMap.get(memberId) ?? memberId
2501
+ }));
2502
+ const groups = await Promise.all(
2503
+ groupInputs.map(async (code) => {
2504
+ const g = await resolveMemberGroup(client, projectId, code);
2505
+ return { groupId: g.id, code: g.code, projectCode };
2506
+ })
2507
+ );
2508
+ mentionPrefix = prependMentions("", members, groups, me).trimEnd();
2509
+ }
2107
2510
  let edited = await readBodyInputOrNull(opts);
2108
2511
  if (edited == null) {
2109
- const original = comment.body.content;
2110
- edited = await openInEditor(original);
2111
- if (original === edited) {
2512
+ const rawContent = comment.body.content;
2513
+ const editorSeed = mentionPrefix ? mentionPrefix + " " + rawContent : rawContent;
2514
+ edited = await openInEditor(editorSeed);
2515
+ if (rawContent === edited) {
2112
2516
  process.stdout.write("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C\n");
2113
2517
  return;
2114
2518
  }
2519
+ } else if (mentionPrefix) {
2520
+ edited = mentionPrefix + " " + edited;
2115
2521
  }
2116
2522
  startSpinner("\uB313\uAE00 \uC218\uC815 \uC911...");
2117
2523
  await client.updatePostComment(projectId, postId, commentId, {
@@ -2123,8 +2529,8 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
2123
2529
  });
2124
2530
 
2125
2531
  // src/commands/post/comment/delete.ts
2126
- var import_commander18 = require("commander");
2127
- var commentDeleteCommand = new import_commander18.Command("delete").description("\uB313\uAE00 \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2532
+ var import_commander21 = require("commander");
2533
+ var commentDeleteCommand = new import_commander21.Command("delete").description("\uB313\uAE00 \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2128
2534
  const config = await getConfigOrThrow();
2129
2535
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2130
2536
  let projectArg;
@@ -2179,14 +2585,348 @@ var commentDeleteCommand = new import_commander18.Command("delete").description(
2179
2585
  `);
2180
2586
  });
2181
2587
 
2182
- // src/commands/post/file/list.ts
2183
- var import_commander19 = require("commander");
2588
+ // src/commands/post/comment/file/index.ts
2589
+ var import_commander26 = require("commander");
2590
+
2591
+ // src/commands/post/comment/file/list.ts
2592
+ var import_commander22 = require("commander");
2184
2593
  function formatSize(bytes) {
2185
2594
  if (bytes < 1024) return `${bytes}B`;
2186
2595
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
2187
2596
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
2188
2597
  }
2189
- var fileListCommand = new import_commander19.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
2598
+ var listCommentFileCommand = new import_commander22.Command("list").description("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 (positional \uBAA8\uB4DC)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2599
+ const globalOpts = listCommentFileCommand.optsWithGlobals();
2600
+ const config = await getConfigOrThrow();
2601
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2602
+ let projectArg;
2603
+ let postNumberArg;
2604
+ let logId = opts.commentId;
2605
+ if (opts.id || opts.url) {
2606
+ if (arg2 || arg3) {
2607
+ throw new DoorayCliError(
2608
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. --comment-id \uC635\uC158 \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
2609
+ EXIT_PARAM_ERROR
2610
+ );
2611
+ }
2612
+ logId = logId ?? arg1;
2613
+ } else if (arg3) {
2614
+ projectArg = arg1;
2615
+ postNumberArg = arg2;
2616
+ logId = logId ?? arg3;
2617
+ } else if (arg1 && !arg2) {
2618
+ projectArg = arg1;
2619
+ if (!logId) {
2620
+ throw new DoorayCliError(
2621
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --comment-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2622
+ EXIT_PARAM_ERROR
2623
+ );
2624
+ }
2625
+ } else {
2626
+ projectArg = arg1;
2627
+ postNumberArg = arg2;
2628
+ if (!logId) {
2629
+ throw new DoorayCliError(
2630
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2631
+ EXIT_PARAM_ERROR
2632
+ );
2633
+ }
2634
+ }
2635
+ if (!logId) {
2636
+ throw new DoorayCliError("<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2637
+ }
2638
+ startSpinner("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
2639
+ const { projectId, postId } = await resolvePostInput(client, {
2640
+ projectArg,
2641
+ postNumberArg,
2642
+ idOpt: opts.id,
2643
+ urlOpt: opts.url
2644
+ });
2645
+ const res = await client.getPostComment(projectId, postId, logId);
2646
+ const files = res.result.files ?? [];
2647
+ stopSpinner(true, `\uCCA8\uBD80 \uD30C\uC77C ${files.length}\uAC1C`);
2648
+ if (files.length === 0) {
2649
+ if (globalOpts.json) {
2650
+ process.stdout.write("[]\n");
2651
+ } else if (!globalOpts.quiet) {
2652
+ process.stdout.write("\uCCA8\uBD80 \uC5C6\uC74C\n");
2653
+ }
2654
+ return;
2655
+ }
2656
+ output(globalOpts, {
2657
+ headers: ["\uD30C\uC77C\uBA85", "\uD06C\uAE30", "ID"],
2658
+ rows: files.map((f) => [f.name, formatSize(f.size), f.id]),
2659
+ raw: files,
2660
+ ids: files.map((f) => f.id)
2661
+ });
2662
+ });
2663
+
2664
+ // src/commands/post/comment/file/upload.ts
2665
+ var import_commander23 = require("commander");
2666
+ var import_node_path4 = require("path");
2667
+
2668
+ // src/utils/comment-files.ts
2669
+ function appendFileReference(body, fileName, fileId) {
2670
+ const safeName = fileName.replace(/[\[\]]/g, "");
2671
+ const ref = `![${safeName}](/files/${fileId})`;
2672
+ if (body.length === 0) return ref;
2673
+ const trailing = body.endsWith("\n") ? "" : "\n";
2674
+ return `${body}${trailing}
2675
+ ${ref}`;
2676
+ }
2677
+ function removeFileReference(body, fileId) {
2678
+ const escaped = fileId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2679
+ const refSource = `!\\[[^\\]]*\\]\\(/files/${escaped}\\)`;
2680
+ const lineRe = new RegExp(`^[ \\t]*${refSource}[ \\t]*$\\n?`, "gm");
2681
+ const inlineRe = new RegExp(refSource, "g");
2682
+ return body.replace(lineRe, "").replace(inlineRe, "");
2683
+ }
2684
+
2685
+ // src/commands/post/comment/file/upload.ts
2686
+ var uploadCommentFileCommand = new import_commander23.Command("upload").description("\uB313\uAE00\uC5D0 \uD30C\uC77C \uC5C5\uB85C\uB4DC (post-level \uD30C\uC77C \uC5C5\uB85C\uB4DC + \uB313\uAE00 reference \uCD94\uAC00, ADR-024)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uB313\uAE00 ID (positional \uBAA8\uB4DC)").argument("[arg4]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, arg4, opts) => {
2687
+ const globalOpts = uploadCommentFileCommand.optsWithGlobals();
2688
+ const config = await getConfigOrThrow();
2689
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2690
+ let projectArg;
2691
+ let postNumberArg;
2692
+ let logId = opts.commentId;
2693
+ let filePath = opts.file;
2694
+ if (opts.id || opts.url) {
2695
+ if (arg3 || arg4) {
2696
+ throw new DoorayCliError(
2697
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC640 \uD30C\uC77C \uACBD\uB85C \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2698
+ EXIT_PARAM_ERROR
2699
+ );
2700
+ }
2701
+ logId = logId ?? arg1;
2702
+ filePath = filePath ?? arg2;
2703
+ } else if (arg4) {
2704
+ projectArg = arg1;
2705
+ postNumberArg = arg2;
2706
+ logId = logId ?? arg3;
2707
+ filePath = filePath ?? arg4;
2708
+ } else if (arg3) {
2709
+ projectArg = arg1;
2710
+ postNumberArg = arg2;
2711
+ logId = logId ?? arg3;
2712
+ } else if (arg1 && !arg2) {
2713
+ projectArg = arg1;
2714
+ } else {
2715
+ projectArg = arg1;
2716
+ postNumberArg = arg2;
2717
+ }
2718
+ if (!logId) {
2719
+ throw new DoorayCliError(
2720
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2721
+ EXIT_PARAM_ERROR
2722
+ );
2723
+ }
2724
+ if (!filePath) {
2725
+ throw new DoorayCliError(
2726
+ "<path>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 4\uBC88\uC9F8 \uB610\uB294 --file \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2727
+ EXIT_PARAM_ERROR
2728
+ );
2729
+ }
2730
+ const { projectId, postId } = await resolvePostInput(client, {
2731
+ projectArg,
2732
+ postNumberArg,
2733
+ idOpt: opts.id,
2734
+ urlOpt: opts.url
2735
+ });
2736
+ startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
2737
+ const uploadRes = await client.uploadPostFile(projectId, postId, filePath);
2738
+ const fileId = uploadRes.result.id;
2739
+ const fileName = (0, import_node_path4.basename)(filePath);
2740
+ stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
2741
+ startSpinner("\uB313\uAE00 reference \uCD94\uAC00 \uC911...");
2742
+ try {
2743
+ const commentRes = await client.getPostComment(projectId, postId, logId);
2744
+ const currentBody = commentRes.result.body.content;
2745
+ const newBody = appendFileReference(currentBody, fileName, fileId);
2746
+ await client.updatePostComment(projectId, postId, logId, {
2747
+ body: { mimeType: "text/x-markdown", content: newBody }
2748
+ });
2749
+ stopSpinner(true, "reference \uCD94\uAC00 \uC644\uB8CC");
2750
+ } catch {
2751
+ stopSpinner(false, "");
2752
+ throw new DoorayCliError(
2753
+ `\uC5C5\uB85C\uB4DC OK / \uB313\uAE00 reference \uCD94\uAC00 \uC2E4\uD328. fileId=${fileId} \u2014 'dooray post comment file delete' \uB610\uB294 \uC218\uB3D9 PUT \uC73C\uB85C \uC815\uB9AC\uD558\uC138\uC694.`,
2754
+ EXIT_API_ERROR
2755
+ );
2756
+ }
2757
+ if (globalOpts.json) {
2758
+ printJson({ fileId, fileName, commentId: logId });
2759
+ } else if (globalOpts.quiet) {
2760
+ process.stdout.write(`${fileId}
2761
+ `);
2762
+ } else {
2763
+ process.stdout.write(`\uC5C5\uB85C\uB4DC + \uB313\uAE00 reference \uCD94\uAC00: fileId=${fileId}
2764
+ `);
2765
+ }
2766
+ });
2767
+
2768
+ // src/commands/post/comment/file/download.ts
2769
+ var import_commander24 = require("commander");
2770
+ var import_promises8 = require("fs/promises");
2771
+ var import_node_path5 = require("path");
2772
+ var downloadCommentFileCommand = new import_commander24.Command("download").description(
2773
+ "\uB313\uAE00\uC5D0 \uCCA8\uBD80\uB41C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC (\uD604\uC7AC comment-id \uB294 \uBBF8\uC0AC\uC6A9 \u2014 \uBA58\uD0C8 \uBAA8\uB378 \uC77C\uAD00\uC131, \uBBF8\uB798 \uB313\uAE00 \uB2E8\uC704 \uAD8C\uD55C \uD638\uD658\uC6A9)"
2774
+ ).argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uB313\uAE00 ID (positional \uBAA8\uB4DC)").argument("[arg4]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("--out <path>", "\uC800\uC7A5 \uACBD\uB85C (\uBBF8\uC9C0\uC815 \uC2DC \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC6D0\uBCF8 \uD30C\uC77C\uBA85\uC73C\uB85C \uC800\uC7A5)", ".").action(async (arg1, arg2, arg3, arg4, opts) => {
2775
+ const config = await getConfigOrThrow();
2776
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2777
+ let projectArg;
2778
+ let postNumberArg;
2779
+ let _commentId = opts.commentId;
2780
+ let fileId = opts.fileId;
2781
+ if (opts.id || opts.url) {
2782
+ if (arg3 || arg4) {
2783
+ throw new DoorayCliError(
2784
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC640 \uD30C\uC77C ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2785
+ EXIT_PARAM_ERROR
2786
+ );
2787
+ }
2788
+ _commentId = _commentId ?? arg1;
2789
+ fileId = fileId ?? arg2;
2790
+ } else if (arg4) {
2791
+ projectArg = arg1;
2792
+ postNumberArg = arg2;
2793
+ _commentId = _commentId ?? arg3;
2794
+ fileId = fileId ?? arg4;
2795
+ } else if (arg3) {
2796
+ projectArg = arg1;
2797
+ postNumberArg = arg2;
2798
+ _commentId = _commentId ?? arg3;
2799
+ } else if (arg1 && !arg2) {
2800
+ projectArg = arg1;
2801
+ } else {
2802
+ projectArg = arg1;
2803
+ postNumberArg = arg2;
2804
+ }
2805
+ if (!fileId) {
2806
+ throw new DoorayCliError(
2807
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 4\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2808
+ EXIT_PARAM_ERROR
2809
+ );
2810
+ }
2811
+ startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
2812
+ const { projectId, postId } = await resolvePostInput(client, {
2813
+ projectArg,
2814
+ postNumberArg,
2815
+ idOpt: opts.id,
2816
+ urlOpt: opts.url
2817
+ });
2818
+ const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
2819
+ const safeName = (0, import_node_path5.basename)(fileName);
2820
+ const outputPath = (0, import_node_path5.join)(opts.out, safeName);
2821
+ await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
2822
+ stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
2823
+ process.stdout.write(`${outputPath}
2824
+ `);
2825
+ });
2826
+
2827
+ // src/commands/post/comment/file/delete.ts
2828
+ var import_commander25 = require("commander");
2829
+ var deleteCommentFileCommand = new import_commander25.Command("delete").description("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uC0AD\uC81C (\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 + \uD30C\uC77C \uC0AD\uC81C, ADR-024)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uB313\uAE00 ID (positional \uBAA8\uB4DC)").argument("[arg4]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("--yes", "\uD655\uC778 \uC5C6\uC774 \uC0AD\uC81C").action(async (arg1, arg2, arg3, arg4, opts) => {
2830
+ const config = await getConfigOrThrow();
2831
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2832
+ let projectArg;
2833
+ let postNumberArg;
2834
+ let logId = opts.commentId;
2835
+ let fileId = opts.fileId;
2836
+ if (opts.id || opts.url) {
2837
+ if (arg3 || arg4) {
2838
+ throw new DoorayCliError(
2839
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC640 \uD30C\uC77C ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2840
+ EXIT_PARAM_ERROR
2841
+ );
2842
+ }
2843
+ logId = logId ?? arg1;
2844
+ fileId = fileId ?? arg2;
2845
+ } else if (arg4) {
2846
+ projectArg = arg1;
2847
+ postNumberArg = arg2;
2848
+ logId = logId ?? arg3;
2849
+ fileId = fileId ?? arg4;
2850
+ } else if (arg3) {
2851
+ projectArg = arg1;
2852
+ postNumberArg = arg2;
2853
+ logId = logId ?? arg3;
2854
+ } else if (arg1 && !arg2) {
2855
+ projectArg = arg1;
2856
+ } else {
2857
+ projectArg = arg1;
2858
+ postNumberArg = arg2;
2859
+ }
2860
+ if (!logId) {
2861
+ throw new DoorayCliError(
2862
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2863
+ EXIT_PARAM_ERROR
2864
+ );
2865
+ }
2866
+ if (!fileId) {
2867
+ throw new DoorayCliError(
2868
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 4\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2869
+ EXIT_PARAM_ERROR
2870
+ );
2871
+ }
2872
+ if (!opts.yes) {
2873
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
2874
+ const ok = await confirm2({
2875
+ message: `\uB313\uAE00 \uBCF8\uBB38\uC5D0\uC11C reference \uB97C \uC81C\uAC70\uD558\uACE0 \uD30C\uC77C(${fileId})\uC744 \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uAC19\uC740 post \uC758 \uB2E4\uB978 \uB313\uAE00\uC5D0\uC11C \uAC19\uC740 \uD30C\uC77C ID \uB97C \uCC38\uC870\uD558\uB294 \uACBD\uC6B0 broken \uB429\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`
2876
+ });
2877
+ if (!ok) {
2878
+ process.stdout.write("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n");
2879
+ return;
2880
+ }
2881
+ }
2882
+ const { projectId, postId } = await resolvePostInput(client, {
2883
+ projectArg,
2884
+ postNumberArg,
2885
+ idOpt: opts.id,
2886
+ urlOpt: opts.url
2887
+ });
2888
+ startSpinner("\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 \uC911...");
2889
+ try {
2890
+ const commentRes = await client.getPostComment(projectId, postId, logId);
2891
+ const currentBody = commentRes.result.body.content;
2892
+ const newBody = removeFileReference(currentBody, fileId);
2893
+ await client.updatePostComment(projectId, postId, logId, {
2894
+ body: { mimeType: "text/x-markdown", content: newBody }
2895
+ });
2896
+ stopSpinner(true, "reference \uC81C\uAC70 \uC644\uB8CC");
2897
+ } catch {
2898
+ stopSpinner(false, "");
2899
+ throw new DoorayCliError(
2900
+ `\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 \uC2E4\uD328. \uD30C\uC77C \uC0AD\uC81C\uB97C \uC9C4\uD589\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. fileId=${fileId}`,
2901
+ EXIT_API_ERROR
2902
+ );
2903
+ }
2904
+ startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
2905
+ try {
2906
+ await client.deletePostFile(projectId, postId, fileId);
2907
+ stopSpinner(true, "\uD30C\uC77C \uC0AD\uC81C \uC644\uB8CC");
2908
+ } catch {
2909
+ stopSpinner(false, "");
2910
+ throw new DoorayCliError(
2911
+ `\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 OK / \uD30C\uC77C \uC0AD\uC81C \uC2E4\uD328. 'dooray post file delete' \uB85C \uD6C4\uCC98\uB9AC\uD558\uC138\uC694. fileId=${fileId}`,
2912
+ EXIT_API_ERROR
2913
+ );
2914
+ }
2915
+ process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uB313\uAE00(${logId})\uC5D0\uC11C \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
2916
+ `);
2917
+ });
2918
+
2919
+ // src/commands/post/comment/file/index.ts
2920
+ var commentFileCommand = new import_commander26.Command("file").description("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uAD00\uB9AC (post-level files API + \uB313\uAE00 PUT \uD569\uC131, ADR-024)").addCommand(listCommentFileCommand).addCommand(uploadCommentFileCommand).addCommand(downloadCommentFileCommand).addCommand(deleteCommentFileCommand);
2921
+
2922
+ // src/commands/post/file/list.ts
2923
+ var import_commander27 = require("commander");
2924
+ function formatSize2(bytes) {
2925
+ if (bytes < 1024) return `${bytes}B`;
2926
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
2927
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
2928
+ }
2929
+ var fileListCommand = new import_commander27.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
2190
2930
  const globalOpts = fileListCommand.optsWithGlobals();
2191
2931
  const config = await getConfigOrThrow();
2192
2932
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2204,7 +2944,7 @@ var fileListCommand = new import_commander19.Command("list").description("\uC5C5
2204
2944
  rows: res.result.map((f) => [
2205
2945
  f.id,
2206
2946
  f.name,
2207
- formatSize(f.size),
2947
+ formatSize2(f.size),
2208
2948
  f.mimeType,
2209
2949
  f.createdAt
2210
2950
  ]),
@@ -2214,10 +2954,10 @@ var fileListCommand = new import_commander19.Command("list").description("\uC5C5
2214
2954
  });
2215
2955
 
2216
2956
  // src/commands/post/file/download.ts
2217
- var import_commander20 = require("commander");
2218
- var import_promises8 = require("fs/promises");
2219
- var import_node_path4 = require("path");
2220
- var fileDownloadCommand = new import_commander20.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (arg1, arg2, arg3, opts) => {
2957
+ var import_commander28 = require("commander");
2958
+ var import_promises9 = require("fs/promises");
2959
+ var import_node_path6 = require("path");
2960
+ var fileDownloadCommand = new import_commander28.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (arg1, arg2, arg3, opts) => {
2221
2961
  const config = await getConfigOrThrow();
2222
2962
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2223
2963
  let projectArg;
@@ -2264,18 +3004,18 @@ var fileDownloadCommand = new import_commander20.Command("download").description
2264
3004
  urlOpt: opts.url
2265
3005
  });
2266
3006
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
2267
- const outputPath = (0, import_node_path4.join)(opts.output, fileName);
2268
- await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
3007
+ const outputPath = (0, import_node_path6.join)(opts.output, fileName);
3008
+ await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
2269
3009
  stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
2270
3010
  process.stdout.write(`${outputPath}
2271
3011
  `);
2272
3012
  });
2273
3013
 
2274
3014
  // src/commands/post/file/download-all.ts
2275
- var import_commander21 = require("commander");
2276
- var import_promises9 = require("fs/promises");
2277
- var import_node_path5 = require("path");
2278
- var fileDownloadAllCommand = new import_commander21.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 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
3015
+ var import_commander29 = require("commander");
3016
+ var import_promises10 = require("fs/promises");
3017
+ var import_node_path7 = require("path");
3018
+ var fileDownloadAllCommand = new import_commander29.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 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
2279
3019
  const config = await getConfigOrThrow();
2280
3020
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2281
3021
  const spinner = startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
@@ -2291,13 +3031,13 @@ var fileDownloadAllCommand = new import_commander21.Command("download-all").desc
2291
3031
  process.stdout.write("\uCCA8\uBD80\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
2292
3032
  return;
2293
3033
  }
2294
- await (0, import_promises9.mkdir)(opts.output, { recursive: true });
3034
+ await (0, import_promises10.mkdir)(opts.output, { recursive: true });
2295
3035
  const downloaded = [];
2296
3036
  for (const file of res.result) {
2297
3037
  spinner.text = `\uB2E4\uC6B4\uB85C\uB4DC \uC911: ${file.name} (${downloaded.length + 1}/${res.result.length})`;
2298
3038
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, file.id);
2299
- const outputPath = (0, import_node_path5.join)(opts.output, fileName);
2300
- await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
3039
+ const outputPath = (0, import_node_path7.join)(opts.output, fileName);
3040
+ await (0, import_promises10.writeFile)(outputPath, Buffer.from(buffer));
2301
3041
  downloaded.push(outputPath);
2302
3042
  }
2303
3043
  stopSpinner(true, `${downloaded.length}\uAC1C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC`);
@@ -2308,9 +3048,9 @@ var fileDownloadAllCommand = new import_commander21.Command("download-all").desc
2308
3048
  });
2309
3049
 
2310
3050
  // src/commands/post/file/upload.ts
2311
- var import_commander22 = require("commander");
2312
- var import_node_path6 = require("path");
2313
- var fileUploadCommand = new import_commander22.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
3051
+ var import_commander30 = require("commander");
3052
+ var import_node_path8 = require("path");
3053
+ var fileUploadCommand = new import_commander30.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2314
3054
  const globalOpts = fileUploadCommand.optsWithGlobals();
2315
3055
  const config = await getConfigOrThrow();
2316
3056
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2365,14 +3105,14 @@ var fileUploadCommand = new import_commander22.Command("upload").description("\u
2365
3105
  process.stdout.write(`${res.result.id}
2366
3106
  `);
2367
3107
  } else {
2368
- process.stdout.write(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${(0, import_node_path6.basename)(filePath)} (ID: ${res.result.id})
3108
+ process.stdout.write(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${(0, import_node_path8.basename)(filePath)} (ID: ${res.result.id})
2369
3109
  `);
2370
3110
  }
2371
3111
  });
2372
3112
 
2373
3113
  // src/commands/post/file/delete.ts
2374
- var import_commander23 = require("commander");
2375
- var fileDeleteCommand = new import_commander23.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
3114
+ var import_commander31 = require("commander");
3115
+ var fileDeleteCommand = new import_commander31.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2376
3116
  const config = await getConfigOrThrow();
2377
3117
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2378
3118
  let projectArg;
@@ -2425,7 +3165,7 @@ var fileDeleteCommand = new import_commander23.Command("delete").description("\u
2425
3165
  });
2426
3166
 
2427
3167
  // src/commands/wiki/list.ts
2428
- var import_commander24 = require("commander");
3168
+ var import_commander32 = require("commander");
2429
3169
 
2430
3170
  // src/formatters/wiki.ts
2431
3171
  function formatWikiList(wikis, opts) {
@@ -2468,7 +3208,7 @@ function formatWikiPageDetail(page, opts) {
2468
3208
  }
2469
3209
 
2470
3210
  // src/commands/wiki/list.ts
2471
- var wikiListCommand = new import_commander24.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) => {
3211
+ var wikiListCommand = new import_commander32.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) => {
2472
3212
  const globalOpts = wikiListCommand.optsWithGlobals();
2473
3213
  const config = await getConfigOrThrow();
2474
3214
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2482,7 +3222,7 @@ var wikiListCommand = new import_commander24.Command("list").description("\uC704
2482
3222
  });
2483
3223
 
2484
3224
  // src/commands/wiki/pages.ts
2485
- var import_commander25 = require("commander");
3225
+ var import_commander33 = require("commander");
2486
3226
 
2487
3227
  // src/resolvers/wiki.ts
2488
3228
  async function resolveWiki(client, projectCode) {
@@ -2526,7 +3266,7 @@ async function resolveWikiHomePageId(client, wikiId) {
2526
3266
  }
2527
3267
 
2528
3268
  // src/commands/wiki/pages.ts
2529
- var wikiPagesCommand = new import_commander25.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) => {
3269
+ var wikiPagesCommand = new import_commander33.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) => {
2530
3270
  const globalOpts = wikiPagesCommand.optsWithGlobals();
2531
3271
  const config = await getConfigOrThrow();
2532
3272
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2538,8 +3278,8 @@ var wikiPagesCommand = new import_commander25.Command("pages").description("\uC7
2538
3278
  });
2539
3279
 
2540
3280
  // src/commands/wiki/page-get.ts
2541
- var import_commander26 = require("commander");
2542
- var wikiPageGetCommand = new import_commander26.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) => {
3281
+ var import_commander34 = require("commander");
3282
+ var wikiPageGetCommand = new import_commander34.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) => {
2543
3283
  const globalOpts = wikiPageGetCommand.optsWithGlobals();
2544
3284
  const config = await getConfigOrThrow();
2545
3285
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2551,8 +3291,8 @@ var wikiPageGetCommand = new import_commander26.Command("get").description("\uC7
2551
3291
  });
2552
3292
 
2553
3293
  // src/commands/wiki/page-create.ts
2554
- var import_commander27 = require("commander");
2555
- var wikiPageCreateCommand = new import_commander27.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) => {
3294
+ var import_commander35 = require("commander");
3295
+ var wikiPageCreateCommand = new import_commander35.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) => {
2556
3296
  const globalOpts = wikiPageCreateCommand.optsWithGlobals();
2557
3297
  const config = await getConfigOrThrow();
2558
3298
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2577,8 +3317,8 @@ var wikiPageCreateCommand = new import_commander27.Command("create").description
2577
3317
  });
2578
3318
 
2579
3319
  // src/commands/wiki/page-edit.ts
2580
- var import_commander28 = require("commander");
2581
- var wikiPageEditCommand = new import_commander28.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 (\uD50C\uB798\uADF8 \uC5C6\uC73C\uBA74 $EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").option("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9 (\uC9C0\uC815 \uC2DC $EDITOR \uC0DD\uB7B5)").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, pageId, opts) => {
3320
+ var import_commander36 = require("commander");
3321
+ var wikiPageEditCommand = new import_commander36.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 (\uD50C\uB798\uADF8 \uC5C6\uC73C\uBA74 $EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").option("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9 (\uC9C0\uC815 \uC2DC $EDITOR \uC0DD\uB7B5)").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, pageId, opts) => {
2582
3322
  const config = await getConfigOrThrow();
2583
3323
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2584
3324
  const hasTitle = opts.title != null;
@@ -2631,10 +3371,10 @@ var wikiPageEditCommand = new import_commander28.Command("edit").description("\u
2631
3371
  });
2632
3372
 
2633
3373
  // src/commands/member/index.ts
2634
- var import_commander31 = require("commander");
3374
+ var import_commander40 = require("commander");
2635
3375
 
2636
3376
  // src/commands/member/get.ts
2637
- var import_commander29 = require("commander");
3377
+ var import_commander37 = require("commander");
2638
3378
 
2639
3379
  // src/formatters/member.ts
2640
3380
  function formatMemberDetail(member, opts) {
@@ -2664,9 +3404,23 @@ function formatMemberList(rows, opts) {
2664
3404
  ids: rows.map((r) => r.id)
2665
3405
  });
2666
3406
  }
3407
+ function formatMemberSearchResults(members, opts) {
3408
+ output(opts, {
3409
+ headers: ["ID", "Name", "UserCode", "Nickname", "Email"],
3410
+ rows: members.map((m) => [
3411
+ m.id,
3412
+ m.name,
3413
+ m.userCode ?? "",
3414
+ m.nickname ?? "",
3415
+ m.externalEmailAddress ?? ""
3416
+ ]),
3417
+ raw: members,
3418
+ ids: members.map((m) => m.id)
3419
+ });
3420
+ }
2667
3421
 
2668
3422
  // src/commands/member/get.ts
2669
- var memberGetCommand = new import_commander29.Command("get").description("\uBA64\uBC84 \uC0C1\uC138 \uC815\uBCF4 \uC870\uD68C (organizationMemberId)").argument("<member-id>", "\uC870\uD68C\uD560 organization member ID").action(async (memberId) => {
3423
+ var memberGetCommand = new import_commander37.Command("get").description("\uBA64\uBC84 \uC0C1\uC138 \uC815\uBCF4 \uC870\uD68C (organizationMemberId)").argument("<member-id>", "\uC870\uD68C\uD560 organization member ID").action(async (memberId) => {
2670
3424
  const globalOpts = memberGetCommand.optsWithGlobals();
2671
3425
  const config = await getConfigOrThrow();
2672
3426
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2675,8 +3429,8 @@ var memberGetCommand = new import_commander29.Command("get").description("\uBA64
2675
3429
  });
2676
3430
 
2677
3431
  // src/commands/member/list.ts
2678
- var import_commander30 = require("commander");
2679
- var memberListCommand = new import_commander30.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uBAA9\uB85D").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
3432
+ var import_commander38 = require("commander");
3433
+ var memberListCommand = new import_commander38.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uBAA9\uB85D").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
2680
3434
  const globalOpts = memberListCommand.optsWithGlobals();
2681
3435
  const config = await getConfigOrThrow();
2682
3436
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2690,13 +3444,48 @@ var memberListCommand = new import_commander30.Command("list").description("\uD5
2690
3444
  );
2691
3445
  });
2692
3446
 
3447
+ // src/commands/member/search.ts
3448
+ var import_commander39 = require("commander");
3449
+ var memberSearchCommand = new import_commander39.Command("search").description("organization \uC804\uCCB4\uC5D0\uC11C \uBA64\uBC84 \uAC80\uC0C9").argument("[keyword]", "\uC774\uB984 \uAC80\uC0C9\uC5B4 (--email/--user-code \uBBF8\uC9C0\uC815 \uC2DC name\uC73C\uB85C \uAC80\uC0C9)").option("--email <email>", "\uC678\uBD80 \uC774\uBA54\uC77C (\uCF64\uB9C8 \uAD6C\uBD84 \uAC00\uB2A5, exact match)").option("--user-code <code>", "\uC0AC\uBC88 like \uAC80\uC0C9").option("--user-code-exact <code>", "\uC0AC\uBC88 exact match").option("--page <n>", "\uD398\uC774\uC9C0 (\uAE30\uBCF8 0)", "0").option("--size <n>", "\uD398\uC774\uC9C0 \uD06C\uAE30 (\uAE30\uBCF8 20, max 100)", "20").action(async (keyword, opts) => {
3450
+ const globalOpts = memberSearchCommand.optsWithGlobals();
3451
+ const filterCount = [keyword, opts.email, opts.userCode, opts.userCodeExact].filter(Boolean).length;
3452
+ if (filterCount === 0) {
3453
+ throw new DoorayCliError(
3454
+ "\uAC80\uC0C9 \uC870\uAC74\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. positional <keyword>(=name) \uB610\uB294 --email/--user-code/--user-code-exact \uC911 \uD558\uB098 \uC774\uC0C1.",
3455
+ EXIT_PARAM_ERROR
3456
+ );
3457
+ }
3458
+ if (keyword && (opts.email || opts.userCode || opts.userCodeExact)) {
3459
+ throw new DoorayCliError(
3460
+ "positional <keyword>(name)\uC640 --email/--user-code/--user-code-exact \uC635\uC158\uC740 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00.",
3461
+ EXIT_PARAM_ERROR
3462
+ );
3463
+ }
3464
+ const config = await getConfigOrThrow();
3465
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
3466
+ const size = Math.min(Math.max(1, Number(opts.size) || 20), 100);
3467
+ const page = Math.max(0, Number(opts.page) || 0);
3468
+ startSpinner("\uBA64\uBC84 \uAC80\uC0C9 \uC911...");
3469
+ const res = await client.searchMembers({
3470
+ ...keyword && { name: keyword },
3471
+ ...opts.email && { externalEmailAddresses: opts.email },
3472
+ ...opts.userCode && { userCode: opts.userCode },
3473
+ ...opts.userCodeExact && { userCodeExact: opts.userCodeExact },
3474
+ page,
3475
+ size
3476
+ });
3477
+ stopSpinner(true, `${res.totalCount}\uAC74 (\uC774 \uD398\uC774\uC9C0 ${res.result.length})`);
3478
+ formatMemberSearchResults(res.result, globalOpts);
3479
+ });
3480
+
2693
3481
  // src/commands/member/index.ts
2694
- var memberCommand = new import_commander31.Command("member").description("Dooray \uBA64\uBC84 \uC870\uD68C");
3482
+ var memberCommand = new import_commander40.Command("member").description("Dooray \uBA64\uBC84 \uC870\uD68C");
2695
3483
  memberCommand.addCommand(memberGetCommand);
2696
3484
  memberCommand.addCommand(memberListCommand);
3485
+ memberCommand.addCommand(memberSearchCommand);
2697
3486
 
2698
3487
  // src/commands/mail/list.ts
2699
- var import_commander32 = require("commander");
3488
+ var import_commander41 = require("commander");
2700
3489
 
2701
3490
  // src/api/imapClient.ts
2702
3491
  var import_imapflow = require("imapflow");
@@ -2804,7 +3593,7 @@ async function getMail(config, uid) {
2804
3593
 
2805
3594
  // src/commands/mail/list.ts
2806
3595
  var import_chalk5 = __toESM(require("chalk"));
2807
- var mailListCommand = new import_commander32.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) => {
3596
+ var mailListCommand = new import_commander41.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) => {
2808
3597
  const globalOpts = mailListCommand.optsWithGlobals();
2809
3598
  const config = await getConfigOrThrow();
2810
3599
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2836,9 +3625,9 @@ var mailListCommand = new import_commander32.Command("list").description("\uBA54
2836
3625
  });
2837
3626
 
2838
3627
  // src/commands/mail/get.ts
2839
- var import_commander33 = require("commander");
3628
+ var import_commander42 = require("commander");
2840
3629
  var import_chalk6 = __toESM(require("chalk"));
2841
- var mailGetCommand = new import_commander33.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
3630
+ var mailGetCommand = new import_commander42.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
2842
3631
  const globalOpts = mailGetCommand.optsWithGlobals();
2843
3632
  const config = await getConfigOrThrow();
2844
3633
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2869,8 +3658,8 @@ ${mail.body}
2869
3658
  });
2870
3659
 
2871
3660
  // src/commands/mail/send.ts
2872
- var import_commander34 = require("commander");
2873
- var import_promises10 = require("fs/promises");
3661
+ var import_commander43 = require("commander");
3662
+ var import_promises11 = require("fs/promises");
2874
3663
 
2875
3664
  // src/api/smtpClient.ts
2876
3665
  var import_nodemailer = __toESM(require("nodemailer"));
@@ -2919,12 +3708,12 @@ async function sendMail(config, opts) {
2919
3708
  }
2920
3709
 
2921
3710
  // src/commands/mail/send.ts
2922
- var mailSendCommand = new import_commander34.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) => {
3711
+ var mailSendCommand = new import_commander43.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) => {
2923
3712
  const globalOpts = mailSendCommand.optsWithGlobals();
2924
3713
  const config = await getConfigOrThrow();
2925
3714
  let body = opts.body ?? "";
2926
3715
  if (opts.bodyFile) {
2927
- body = await (0, import_promises10.readFile)(opts.bodyFile, "utf-8");
3716
+ body = await (0, import_promises11.readFile)(opts.bodyFile, "utf-8");
2928
3717
  }
2929
3718
  if (!body) {
2930
3719
  process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
@@ -2954,8 +3743,8 @@ var mailSendCommand = new import_commander34.Command("send").description("\uBA54
2954
3743
  });
2955
3744
 
2956
3745
  // src/commands/mail/reply.ts
2957
- var import_commander35 = require("commander");
2958
- var import_promises11 = require("fs/promises");
3746
+ var import_commander44 = require("commander");
3747
+ var import_promises12 = require("fs/promises");
2959
3748
  var import_imapflow2 = require("imapflow");
2960
3749
  async function getMessageId(config, uid) {
2961
3750
  const imap = getImapConfigOrThrow(config);
@@ -2982,12 +3771,12 @@ async function getMessageId(config, uid) {
2982
3771
  await client.logout();
2983
3772
  }
2984
3773
  }
2985
- var mailReplyCommand = new import_commander35.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) => {
3774
+ var mailReplyCommand = new import_commander44.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) => {
2986
3775
  const globalOpts = mailReplyCommand.optsWithGlobals();
2987
3776
  const config = await getConfigOrThrow();
2988
3777
  let body = opts.body ?? "";
2989
3778
  if (opts.bodyFile) {
2990
- body = await (0, import_promises11.readFile)(opts.bodyFile, "utf-8");
3779
+ body = await (0, import_promises12.readFile)(opts.bodyFile, "utf-8");
2991
3780
  }
2992
3781
  if (!body) {
2993
3782
  process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
@@ -3022,20 +3811,299 @@ var mailReplyCommand = new import_commander35.Command("reply").description("\uBA
3022
3811
  }
3023
3812
  });
3024
3813
 
3814
+ // src/commands/feedback.ts
3815
+ var import_commander45 = require("commander");
3816
+ var import_node_child_process2 = require("child_process");
3817
+ var import_node_util = require("util");
3818
+ var import_promises15 = require("fs/promises");
3819
+ var import_node_os4 = require("os");
3820
+ var import_node_path11 = require("path");
3821
+ var import_node_crypto = require("crypto");
3822
+ var import_prompts = require("@inquirer/prompts");
3823
+
3824
+ // src/utils/feedback-meta.ts
3825
+ var import_promises13 = require("fs/promises");
3826
+ var import_node_path9 = require("path");
3827
+ async function readCliVersion() {
3828
+ const entry = process.argv[1];
3829
+ const here = entry ? (0, import_node_path9.dirname)(entry) : process.cwd();
3830
+ const candidates = [
3831
+ (0, import_node_path9.join)(here, "package.json"),
3832
+ (0, import_node_path9.join)(here, "..", "package.json"),
3833
+ (0, import_node_path9.join)(here, "..", "..", "package.json")
3834
+ ];
3835
+ for (const p of candidates) {
3836
+ try {
3837
+ const raw = await (0, import_promises13.readFile)(p, "utf-8");
3838
+ const pkg = JSON.parse(raw);
3839
+ if (pkg.name === "@bifos/dooray-cli" && typeof pkg.version === "string") {
3840
+ return pkg.version;
3841
+ }
3842
+ } catch {
3843
+ }
3844
+ }
3845
+ return "unknown";
3846
+ }
3847
+ function collectMeta(version) {
3848
+ return {
3849
+ cliVersion: version,
3850
+ nodeVersion: process.version,
3851
+ os: process.platform,
3852
+ arch: process.arch
3853
+ };
3854
+ }
3855
+ function buildLastRunBlock(last) {
3856
+ return [
3857
+ "## \uC9C1\uC804 \uC2E4\uD589 (\uC790\uB3D9 \uCCA8\uBD80)",
3858
+ "",
3859
+ "```",
3860
+ `$ ${last.argv.join(" ")}`,
3861
+ last.errorMessage.replace(/```/g, "'''"),
3862
+ "```",
3863
+ "",
3864
+ `- exit code: ${last.exitCode}`,
3865
+ `- \uC2DC\uAC01: ${last.timestamp}`
3866
+ ].join("\n");
3867
+ }
3868
+ function buildIssueBody(userBody, meta) {
3869
+ return [
3870
+ "## \uD658\uACBD",
3871
+ `- dooray-cli \uBC84\uC804: ${meta.cliVersion}`,
3872
+ `- Node: ${meta.nodeVersion}`,
3873
+ `- OS: ${meta.os} ${meta.arch}`,
3874
+ "",
3875
+ "## \uC0AC\uC6A9\uC790 \uD53C\uB4DC\uBC31",
3876
+ "",
3877
+ userBody.trim(),
3878
+ ""
3879
+ ].join("\n");
3880
+ }
3881
+
3882
+ // src/cache/last-run.ts
3883
+ var import_promises14 = require("fs/promises");
3884
+ var import_node_path10 = require("path");
3885
+ var import_node_os3 = require("os");
3886
+ var LAST_RUN_PATH = (0, import_node_path10.join)((0, import_node_os3.homedir)(), ".dooray", "last-run.json");
3887
+ function isLastRun(obj) {
3888
+ if (typeof obj !== "object" || obj === null) return false;
3889
+ const o = obj;
3890
+ return Array.isArray(o.argv) && o.argv.every((a) => typeof a === "string") && typeof o.exitCode === "number" && typeof o.errorMessage === "string" && typeof o.timestamp === "string";
3891
+ }
3892
+ async function readLastRun() {
3893
+ try {
3894
+ const raw = await (0, import_promises14.readFile)(LAST_RUN_PATH, "utf-8");
3895
+ const parsed = JSON.parse(raw);
3896
+ return isLastRun(parsed) ? parsed : null;
3897
+ } catch {
3898
+ return null;
3899
+ }
3900
+ }
3901
+ async function writeLastRun(data) {
3902
+ await (0, import_promises14.mkdir)((0, import_node_path10.join)((0, import_node_os3.homedir)(), ".dooray"), { recursive: true });
3903
+ const tmp2 = LAST_RUN_PATH + ".tmp";
3904
+ await (0, import_promises14.writeFile)(tmp2, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
3905
+ await (0, import_promises14.rename)(tmp2, LAST_RUN_PATH);
3906
+ }
3907
+
3908
+ // src/commands/feedback.ts
3909
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
3910
+ var TARGET_REPO = "jon890/dooray-cli";
3911
+ async function ensureGhInstalled() {
3912
+ try {
3913
+ await execFileAsync("gh", ["--version"]);
3914
+ } catch {
3915
+ throw new DoorayCliError(
3916
+ "gh CLI\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.\n \uC124\uCE58: brew install gh (\uB610\uB294 https://cli.github.com)\n \uC124\uCE58 \uD6C4: gh auth login",
3917
+ EXIT_PARAM_ERROR
3918
+ );
3919
+ }
3920
+ }
3921
+ async function readBody(opts) {
3922
+ if (opts.body) return opts.body;
3923
+ if (opts.bodyFile) return await (0, import_promises15.readFile)(opts.bodyFile, "utf-8");
3924
+ return void 0;
3925
+ }
3926
+ var feedbackCommand = new import_commander45.Command("feedback").description("dooray-cli\uC5D0 \uB300\uD55C GitHub issue \uB4F1\uB85D (gh CLI \uC704\uC784)").option("--title <text>", "\uC774\uC288 \uC81C\uBAA9 (\uC5C6\uC73C\uBA74 \uC778\uD130\uB799\uD2F0\uBE0C)").option("--body <text>", "\uC774\uC288 \uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option(
3927
+ "--label <name>",
3928
+ "\uB77C\uBCA8 (\uBC18\uBCF5 \uAC00\uB2A5)",
3929
+ (value, prev) => [...prev, value],
3930
+ []
3931
+ ).option("--last", "\uC9C1\uC804 \uC2E4\uD589\uD55C dooray \uBA85\uB839\uC758 argv + \uC5D0\uB7EC\uB97C \uBCF8\uBB38 \uC0C1\uB2E8\uC5D0 \uC790\uB3D9 \uCCA8\uBD80").option("--dry-run", "gh \uD638\uCD9C \uC5C6\uC774 \uBCF8\uBB38\uB9CC \uBBF8\uB9AC\uBCF4\uAE30").action(async (opts) => {
3932
+ let title = opts.title;
3933
+ let userBody = await readBody(opts);
3934
+ let labels = [...opts.label];
3935
+ if (opts.last) {
3936
+ const last = await readLastRun();
3937
+ if (!last) {
3938
+ throw new DoorayCliError(
3939
+ "\uAE30\uB85D\uB41C \uC9C1\uC804 \uC2E4\uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. config.json\uC5D0 trackLastRun: true \uC124\uC815 \uD6C4 dooray \uBA85\uB839 \uC2E4\uD589 \uC2DC \uC790\uB3D9 \uAE30\uB85D\uB429\uB2C8\uB2E4.\n \uC124\uC815: dooray config set track-last-run true",
3940
+ EXIT_PARAM_ERROR
3941
+ );
3942
+ }
3943
+ const lastBlock = buildLastRunBlock(last);
3944
+ if (userBody == null) {
3945
+ userBody = await (0, import_prompts.editor)({
3946
+ message: "\uBCF8\uBB38 \uC791\uC131 ($EDITOR\uAC00 \uC5F4\uB9BC)",
3947
+ default: lastBlock + "\n\n"
3948
+ });
3949
+ } else {
3950
+ userBody = lastBlock + "\n\n" + userBody;
3951
+ }
3952
+ }
3953
+ if (!title) {
3954
+ title = await (0, import_prompts.input)({ message: "\uC774\uC288 \uC81C\uBAA9" });
3955
+ }
3956
+ if (!title || !title.trim()) {
3957
+ throw new DoorayCliError("\uC81C\uBAA9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
3958
+ }
3959
+ if (labels.length === 0 && !opts.title && !opts.body && !opts.bodyFile) {
3960
+ const labelInput = await (0, import_prompts.input)({
3961
+ message: "\uB77C\uBCA8 (\uCF64\uB9C8\uB85C \uC5EC\uB7EC \uAC1C, \uBE44\uC6B0\uBA74 \uC5C6\uC74C)",
3962
+ default: ""
3963
+ });
3964
+ labels = labelInput.split(",").map((s) => s.trim()).filter(Boolean);
3965
+ }
3966
+ if (userBody == null) {
3967
+ userBody = await (0, import_prompts.editor)({
3968
+ message: "\uBCF8\uBB38 \uC791\uC131 ($EDITOR\uAC00 \uC5F4\uB9BC)",
3969
+ default: ""
3970
+ });
3971
+ }
3972
+ if (!userBody.trim()) {
3973
+ throw new DoorayCliError("\uBCF8\uBB38\uC774 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
3974
+ }
3975
+ const version = await readCliVersion();
3976
+ const meta = collectMeta(version);
3977
+ const issueBody = buildIssueBody(userBody, meta);
3978
+ if (opts.dryRun) {
3979
+ process.stdout.write("--- DRY RUN ---\n");
3980
+ process.stdout.write(`Repo: ${TARGET_REPO}
3981
+ `);
3982
+ process.stdout.write(`Title: ${title}
3983
+ `);
3984
+ process.stdout.write(
3985
+ `Labels: ${labels.length > 0 ? labels.join(", ") : "(\uC5C6\uC74C)"}
3986
+ `
3987
+ );
3988
+ process.stdout.write("Body:\n");
3989
+ process.stdout.write(issueBody);
3990
+ process.stdout.write("--- END ---\n");
3991
+ return;
3992
+ }
3993
+ const isInteractive = !opts.title;
3994
+ if (isInteractive) {
3995
+ process.stderr.write("\n--- \uBBF8\uB9AC\uBCF4\uAE30 ---\n");
3996
+ process.stderr.write(`Repo: ${TARGET_REPO}
3997
+ `);
3998
+ process.stderr.write(`Title: ${title}
3999
+ `);
4000
+ process.stderr.write(
4001
+ `Labels: ${labels.length > 0 ? labels.join(", ") : "(\uC5C6\uC74C)"}
4002
+ `
4003
+ );
4004
+ process.stderr.write("Body:\n");
4005
+ process.stderr.write(issueBody);
4006
+ process.stderr.write("--- \uB05D ---\n\n");
4007
+ const ok = await (0, import_prompts.confirm)({
4008
+ message: "\uC774 \uB0B4\uC6A9\uC73C\uB85C \uB4F1\uB85D\uD560\uAE4C\uC694?",
4009
+ default: true
4010
+ });
4011
+ if (!ok) {
4012
+ process.stderr.write("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n");
4013
+ return;
4014
+ }
4015
+ }
4016
+ await ensureGhInstalled();
4017
+ const bodyFile = (0, import_node_path11.join)((0, import_node_os4.tmpdir)(), `dooray-feedback-${(0, import_node_crypto.randomUUID)()}.md`);
4018
+ await (0, import_promises15.writeFile)(bodyFile, issueBody);
4019
+ try {
4020
+ const args = [
4021
+ "issue",
4022
+ "create",
4023
+ "--repo",
4024
+ TARGET_REPO,
4025
+ "--title",
4026
+ title,
4027
+ "--body-file",
4028
+ bodyFile
4029
+ ];
4030
+ for (const l of labels) {
4031
+ args.push("--label", l);
4032
+ }
4033
+ const { stdout } = await execFileAsync("gh", args);
4034
+ process.stdout.write(stdout);
4035
+ } catch (err) {
4036
+ const msg = err instanceof Error ? err.message : String(err);
4037
+ throw new DoorayCliError(
4038
+ `GitHub issue \uC0DD\uC131 \uC2E4\uD328:
4039
+ ${msg}
4040
+
4041
+ gh \uC778\uC99D \uC548 \uB418\uC5B4 \uC788\uC73C\uBA74: gh auth login`,
4042
+ EXIT_PARAM_ERROR
4043
+ );
4044
+ } finally {
4045
+ await (0, import_promises15.unlink)(bodyFile).catch(() => {
4046
+ });
4047
+ }
4048
+ });
4049
+
4050
+ // src/utils/argv-sanitize.ts
4051
+ var KEY_VALUE_PATTERNS = [
4052
+ /^(--api-key|--token|--password)=(.+)$/
4053
+ ];
4054
+ var SEPARATED_KEYS = /* @__PURE__ */ new Set(["--api-key", "--token", "--password"]);
4055
+ function sanitizeArgv(argv) {
4056
+ const out = [];
4057
+ for (let i = 0; i < argv.length; i++) {
4058
+ const a = argv[i];
4059
+ let kvMatch = null;
4060
+ for (const re of KEY_VALUE_PATTERNS) {
4061
+ const m = re.exec(a);
4062
+ if (m) {
4063
+ kvMatch = m;
4064
+ break;
4065
+ }
4066
+ }
4067
+ if (kvMatch) {
4068
+ out.push(`${kvMatch[1]}=***`);
4069
+ continue;
4070
+ }
4071
+ if (SEPARATED_KEYS.has(a)) {
4072
+ out.push(a);
4073
+ if (i + 1 < argv.length) {
4074
+ out.push("***");
4075
+ i++;
4076
+ }
4077
+ continue;
4078
+ }
4079
+ if (/^Authorization\s*:/i.test(a) || /^Bearer\s+\S+/.test(a)) {
4080
+ out.push(a.replace(/(Authorization\s*:\s*).+/i, "$1***").replace(/^(Bearer\s+).+/i, "$1***"));
4081
+ continue;
4082
+ }
4083
+ out.push(a);
4084
+ }
4085
+ return out;
4086
+ }
4087
+
3025
4088
  // src/index.ts
3026
- var program = new import_commander36.Command();
3027
- program.name("dooray").description("Dooray REST API CLI").version("0.5.3").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
4089
+ var program = new import_commander46.Command();
4090
+ program.name("dooray").description("Dooray REST API CLI").version("0.6.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");
3028
4091
  program.hook("preAction", () => {
3029
4092
  const opts = program.opts();
3030
4093
  if (opts.color === false || process.env.NO_COLOR) {
3031
4094
  import_chalk7.default.level = 0;
3032
4095
  }
4096
+ if (opts.json || opts.quiet) {
4097
+ setQuiet(true);
4098
+ }
3033
4099
  });
3034
- var projectCommand = new import_commander36.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
4100
+ var projectCommand = new import_commander46.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
3035
4101
  projectCommand.addCommand(projectListCommand);
3036
4102
  projectCommand.addCommand(projectMembersCommand);
3037
4103
  projectCommand.addCommand(projectWorkflowsCommand);
3038
- var postCommand = new import_commander36.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
4104
+ projectCommand.addCommand(projectGroupsCommand);
4105
+ projectCommand.addCommand(projectTagsCommand);
4106
+ var postCommand = new import_commander46.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
3039
4107
  postCommand.addCommand(postListCommand);
3040
4108
  postCommand.addCommand(postSearchCommand);
3041
4109
  postCommand.addCommand(postGetCommand);
@@ -3043,28 +4111,30 @@ postCommand.addCommand(postEditCommand);
3043
4111
  postCommand.addCommand(postCreateCommand);
3044
4112
  postCommand.addCommand(postDoneCommand);
3045
4113
  postCommand.addCommand(postWorkflowCommand);
3046
- var commentCommand = new import_commander36.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
4114
+ var commentCommand = new import_commander46.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
3047
4115
  commentCommand.addCommand(commentListCommand);
4116
+ commentCommand.addCommand(commentLatestCommand);
3048
4117
  commentCommand.addCommand(commentAddCommand);
3049
4118
  commentCommand.addCommand(commentEditCommand);
3050
4119
  commentCommand.addCommand(commentDeleteCommand);
4120
+ commentCommand.addCommand(commentFileCommand);
3051
4121
  postCommand.addCommand(commentCommand);
3052
- var fileCommand = new import_commander36.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
4122
+ var fileCommand = new import_commander46.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
3053
4123
  fileCommand.addCommand(fileListCommand);
3054
4124
  fileCommand.addCommand(fileDownloadCommand);
3055
4125
  fileCommand.addCommand(fileDownloadAllCommand);
3056
4126
  fileCommand.addCommand(fileUploadCommand);
3057
4127
  fileCommand.addCommand(fileDeleteCommand);
3058
4128
  postCommand.addCommand(fileCommand);
3059
- var wikiCommand = new import_commander36.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
4129
+ var wikiCommand = new import_commander46.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
3060
4130
  wikiCommand.addCommand(wikiListCommand);
3061
4131
  wikiCommand.addCommand(wikiPagesCommand);
3062
- var wikiPageCommand = new import_commander36.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
4132
+ var wikiPageCommand = new import_commander46.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
3063
4133
  wikiPageCommand.addCommand(wikiPageGetCommand);
3064
4134
  wikiPageCommand.addCommand(wikiPageCreateCommand);
3065
4135
  wikiPageCommand.addCommand(wikiPageEditCommand);
3066
4136
  wikiCommand.addCommand(wikiPageCommand);
3067
- var mailCommand = new import_commander36.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
4137
+ var mailCommand = new import_commander46.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
3068
4138
  mailCommand.addCommand(mailListCommand);
3069
4139
  mailCommand.addCommand(mailGetCommand);
3070
4140
  mailCommand.addCommand(mailSendCommand);
@@ -3078,11 +4148,25 @@ program.addCommand(projectCommand);
3078
4148
  program.addCommand(postCommand);
3079
4149
  program.addCommand(wikiCommand);
3080
4150
  program.addCommand(mailCommand);
3081
- program.parseAsync().catch((err) => {
3082
- if (err instanceof DoorayCliError) {
3083
- process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${err.message}`) + "\n");
3084
- process.exit(err.exitCode);
4151
+ program.addCommand(feedbackCommand);
4152
+ program.parseAsync().catch(async (err) => {
4153
+ const errorMessage = err instanceof Error ? err.message : String(err);
4154
+ const exitCode = err instanceof DoorayCliError ? err.exitCode : 1;
4155
+ try {
4156
+ const config = await getConfig();
4157
+ const sanitized = sanitizeArgv(process.argv.slice(2));
4158
+ const firstNonFlag = sanitized.find((a) => !a.startsWith("-"));
4159
+ const isFeedbackCommand = firstNonFlag === "feedback";
4160
+ if (config?.trackLastRun && !isFeedbackCommand) {
4161
+ await writeLastRun({
4162
+ argv: ["dooray", ...sanitized],
4163
+ exitCode,
4164
+ errorMessage,
4165
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4166
+ });
4167
+ }
4168
+ } catch {
3085
4169
  }
3086
- process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${err.message}`) + "\n");
3087
- process.exit(1);
4170
+ process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${errorMessage}`) + "\n");
4171
+ process.exit(exitCode);
3088
4172
  });