@bifos/dooray-cli 0.5.2 → 0.5.4

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_commander33 = require("commander");
27
+ var import_commander40 = require("commander");
28
28
  var import_chalk7 = __toESM(require("chalk"));
29
29
 
30
30
  // src/commands/config.ts
@@ -198,6 +198,9 @@ var PROJECTS_PATH = (0, import_node_path2.join)(CACHE_DIR, "projects.json");
198
198
  var PROJECTS_PRIVATE_PATH = (0, import_node_path2.join)(CACHE_DIR, "projects-private.json");
199
199
  var MEMBERS_DIR = (0, import_node_path2.join)(CACHE_DIR, "members");
200
200
  var WORKFLOWS_DIR = (0, import_node_path2.join)(CACHE_DIR, "workflows");
201
+ var TAGS_DIR = (0, import_node_path2.join)(CACHE_DIR, "tags");
202
+ var MILESTONES_DIR = (0, import_node_path2.join)(CACHE_DIR, "milestones");
203
+ var MEMBER_GROUPS_DIR = (0, import_node_path2.join)(CACHE_DIR, "member-groups");
201
204
  var WIKIS_PATH = (0, import_node_path2.join)(CACHE_DIR, "wikis.json");
202
205
  async function ensureDir2(dir) {
203
206
  await (0, import_promises2.mkdir)(dir, { recursive: true });
@@ -258,6 +261,33 @@ async function getWorkflows(projectId) {
258
261
  async function setWorkflows(projectId, items) {
259
262
  await writeJson(workflowsPath(projectId), { updatedAt: now(), data: items });
260
263
  }
264
+ function tagsPath(projectId) {
265
+ return (0, import_node_path2.join)(TAGS_DIR, `${projectId}.json`);
266
+ }
267
+ async function getTags(projectId) {
268
+ return readJson(tagsPath(projectId));
269
+ }
270
+ async function setTags(projectId, items) {
271
+ await writeJson(tagsPath(projectId), { updatedAt: now(), data: items });
272
+ }
273
+ function milestonesPath(projectId) {
274
+ return (0, import_node_path2.join)(MILESTONES_DIR, `${projectId}.json`);
275
+ }
276
+ async function getMilestones(projectId) {
277
+ return readJson(milestonesPath(projectId));
278
+ }
279
+ async function setMilestones(projectId, items) {
280
+ await writeJson(milestonesPath(projectId), { updatedAt: now(), data: items });
281
+ }
282
+ function memberGroupsPath(projectId) {
283
+ return (0, import_node_path2.join)(MEMBER_GROUPS_DIR, `${projectId}.json`);
284
+ }
285
+ async function getMemberGroups(projectId) {
286
+ return readJson(memberGroupsPath(projectId));
287
+ }
288
+ async function setMemberGroups(projectId, items) {
289
+ await writeJson(memberGroupsPath(projectId), { updatedAt: now(), data: items });
290
+ }
261
291
  async function getWikis() {
262
292
  return readJson(WIKIS_PATH);
263
293
  }
@@ -285,8 +315,26 @@ async function getCacheStats() {
285
315
  workflowProjectCount = files.filter((f) => f.endsWith(".json")).length;
286
316
  } catch {
287
317
  }
318
+ let tagProjectCount = 0;
319
+ try {
320
+ const files = await (0, import_promises2.readdir)(TAGS_DIR);
321
+ tagProjectCount = files.filter((f) => f.endsWith(".json")).length;
322
+ } catch {
323
+ }
324
+ let milestoneProjectCount = 0;
325
+ try {
326
+ const files = await (0, import_promises2.readdir)(MILESTONES_DIR);
327
+ milestoneProjectCount = files.filter((f) => f.endsWith(".json")).length;
328
+ } catch {
329
+ }
330
+ let memberGroupProjectCount = 0;
331
+ try {
332
+ const files = await (0, import_promises2.readdir)(MEMBER_GROUPS_DIR);
333
+ memberGroupProjectCount = files.filter((f) => f.endsWith(".json")).length;
334
+ } catch {
335
+ }
288
336
  const me = await getMe();
289
- return { projectCount, memberProjectCount, workflowProjectCount, me: me?.data ?? null };
337
+ return { projectCount, memberProjectCount, workflowProjectCount, tagProjectCount, milestoneProjectCount, memberGroupProjectCount, me: me?.data ?? null };
290
338
  }
291
339
 
292
340
  // src/commands/cache.ts
@@ -417,6 +465,13 @@ var DoorayApiClient = class {
417
465
  return toDoorayCliError(e);
418
466
  }
419
467
  }
468
+ async getPostStandalone(postId) {
469
+ try {
470
+ return await this.api.get(`project/v1/posts/${postId}`).json();
471
+ } catch (e) {
472
+ return toDoorayCliError(e);
473
+ }
474
+ }
420
475
  async createPost(projectId, body) {
421
476
  try {
422
477
  return await this.api.post(`project/v1/projects/${projectId}/posts`, { json: body }).json();
@@ -520,6 +575,45 @@ var DoorayApiClient = class {
520
575
  return toDoorayCliError(e);
521
576
  }
522
577
  }
578
+ // ─── Tags ───────────────────────────────────────────
579
+ async getProjectTags(projectId, params) {
580
+ try {
581
+ return await this.api.get(`project/v1/projects/${projectId}/tags`, {
582
+ searchParams: {
583
+ ...params?.page != null && { page: params.page },
584
+ ...params?.size != null && { size: params.size }
585
+ }
586
+ }).json();
587
+ } catch (e) {
588
+ return toDoorayCliError(e);
589
+ }
590
+ }
591
+ // ─── Member Groups ──────────────────────────────────
592
+ async getProjectMemberGroups(projectId, params) {
593
+ try {
594
+ return await this.api.get(`project/v1/projects/${projectId}/member-groups`, {
595
+ searchParams: {
596
+ ...params?.page != null && { page: params.page },
597
+ ...params?.size != null && { size: params.size }
598
+ }
599
+ }).json();
600
+ } catch (e) {
601
+ return toDoorayCliError(e);
602
+ }
603
+ }
604
+ // ─── Milestones ─────────────────────────────────────
605
+ async getProjectMilestones(projectId, params) {
606
+ try {
607
+ return await this.api.get(`project/v1/projects/${projectId}/milestones`, {
608
+ searchParams: {
609
+ ...params?.page != null && { page: params.page },
610
+ ...params?.size != null && { size: params.size }
611
+ }
612
+ }).json();
613
+ } catch (e) {
614
+ return toDoorayCliError(e);
615
+ }
616
+ }
523
617
  // ─── Wiki ───────────────────────────────────────────
524
618
  async getWikis(params) {
525
619
  try {
@@ -677,6 +771,10 @@ var PROJECTS_TTL_MS = 36e5;
677
771
  var MEMBERS_TTL_MS = 36e5;
678
772
  var WORKFLOWS_TTL_MS = 864e5;
679
773
  var ME_TTL_MS = 864e5;
774
+ var TAGS_TTL_MS = 864e5;
775
+ var MILESTONES_TTL_MS = 864e5;
776
+ var RESOLVER_FETCH_PAGE_SIZE = 100;
777
+ var MEMBER_GROUPS_TTL_MS = 864e5;
680
778
  var WIKIS_TTL_MS = 864e5;
681
779
 
682
780
  // src/resolvers/me.ts
@@ -717,6 +815,9 @@ var doctorCommand = new import_commander3.Command("doctor").description("\uC124\
717
815
  console.log(` \uD504\uB85C\uC81D\uD2B8: ${stats.projectCount}\uAC1C`);
718
816
  console.log(` \uBA64\uBC84: ${stats.memberProjectCount}\uAC1C \uD504\uB85C\uC81D\uD2B8`);
719
817
  console.log(` \uC6CC\uD06C\uD50C\uB85C\uC6B0: ${stats.workflowProjectCount}\uAC1C \uD504\uB85C\uC81D\uD2B8`);
818
+ console.log(` Tag \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.tagProjectCount}`);
819
+ console.log(` Milestone \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.milestoneProjectCount}`);
820
+ console.log(` Member Group \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.memberGroupProjectCount}`);
720
821
  const claudeDir = import_path.default.join(
721
822
  process.env.HOME ?? process.env.USERPROFILE ?? "",
722
823
  ".claude"
@@ -762,10 +863,10 @@ var import_chalk4 = __toESM(require("chalk"));
762
863
  var import_path2 = __toESM(require("path"));
763
864
  var import_promises5 = __toESM(require("fs/promises"));
764
865
  var setupCommand = new import_commander4.Command("setup").description("\uB300\uD654\uD615 \uCD08\uAE30 \uC124\uC815 \uB9C8\uBC95\uC0AC").action(async () => {
765
- const { input, select, password, confirm } = await import("@inquirer/prompts");
866
+ const { input: input2, select, password, confirm: confirm2 } = await import("@inquirer/prompts");
766
867
  const existing = await getConfig();
767
868
  try {
768
- const tenantName = await input({
869
+ const tenantName = await input2({
769
870
  message: "\uD14C\uB10C\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694",
770
871
  default: existing?.tenantName ?? DEFAULTS.tenantName,
771
872
  theme: { prefix: "\u{1F3E2}" }
@@ -814,7 +915,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
814
915
  );
815
916
  }
816
917
  }
817
- const useMail = await confirm({
918
+ const useMail = await confirm2({
818
919
  message: "\uBA54\uC77C \uAE30\uB2A5\uC744 \uC0AC\uC6A9\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
819
920
  default: !!existing?.imapUsername,
820
921
  theme: { prefix: "\u{1F4E7}" }
@@ -822,7 +923,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
822
923
  let imapUsername;
823
924
  let imapPassword;
824
925
  if (useMail) {
825
- imapUsername = await input({
926
+ imapUsername = await input2({
826
927
  message: `IMAP \uC0AC\uC6A9\uC790 \uC774\uBA54\uC77C (\uD655\uC778: https://${tenantName}.dooray.com/setting/mail/general/read)`,
827
928
  default: existing?.imapUsername,
828
929
  theme: { prefix: "\u{1F4E8}" }
@@ -847,7 +948,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
847
948
  )
848
949
  );
849
950
  } else {
850
- const installSkill = await confirm({
951
+ const installSkill = await confirm2({
851
952
  message: "Claude Code \uC2A4\uD0AC\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
852
953
  default: true,
853
954
  theme: { prefix: "\u{1F527}" }
@@ -939,17 +1040,17 @@ async function ensurePrivateProjects(client) {
939
1040
  await setPrivateProjects(items);
940
1041
  return items;
941
1042
  }
942
- async function resolveProject(client, input) {
1043
+ async function resolveProject(client, input2) {
943
1044
  const projects = await ensureProjects(client);
944
- const match = projects.find((p) => p.code === input || p.id === input);
1045
+ const match = projects.find((p) => p.code === input2 || p.id === input2);
945
1046
  if (match) return match.id;
946
1047
  const privateCached = await getPrivateProjects();
947
1048
  if (privateCached && !isExpired(privateCached.updatedAt, PROJECTS_TTL_MS)) {
948
- const privateMatch = privateCached.data.find((p) => p.code === input || p.id === input);
1049
+ const privateMatch = privateCached.data.find((p) => p.code === input2 || p.id === input2);
949
1050
  if (privateMatch) return privateMatch.id;
950
1051
  }
951
1052
  throw new DoorayCliError(
952
- `\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}
1053
+ `\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input2}
953
1054
  \uAC1C\uC778 \uD504\uB85C\uC81D\uD2B8\uB77C\uBA74: dooray project list --type private \uB85C \uCE90\uC2DC\uB97C \uAC31\uC2E0\uD558\uC138\uC694`,
954
1055
  EXIT_PARAM_ERROR
955
1056
  );
@@ -1024,6 +1125,32 @@ var projectListCommand = new import_commander5.Command("list").description("\uD5
1024
1125
  // src/commands/project/members.ts
1025
1126
  var import_commander6 = require("commander");
1026
1127
 
1128
+ // src/resolvers/match.ts
1129
+ function matchByName(items, input2, label, renderCandidate) {
1130
+ const exact = items.filter((i) => i.name === input2);
1131
+ if (exact.length === 1) return exact[0];
1132
+ if (exact.length > 1) {
1133
+ throw new DoorayCliError(
1134
+ `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4(\uC815\uD655\uC77C\uCE58): "${input2}"
1135
+ ` + exact.map((i) => ` - ${renderCandidate(i)}`).join("\n"),
1136
+ EXIT_PARAM_ERROR
1137
+ );
1138
+ }
1139
+ const partial = items.filter((i) => i.name.includes(input2));
1140
+ if (partial.length === 1) return partial[0];
1141
+ if (partial.length > 1) {
1142
+ throw new DoorayCliError(
1143
+ `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4: "${input2}"
1144
+ ` + partial.map((i) => ` - ${renderCandidate(i)}`).join("\n"),
1145
+ EXIT_PARAM_ERROR
1146
+ );
1147
+ }
1148
+ throw new DoorayCliError(
1149
+ `${label}\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input2}`,
1150
+ EXIT_PARAM_ERROR
1151
+ );
1152
+ }
1153
+
1027
1154
  // src/resolvers/member.ts
1028
1155
  async function fetchAllMembers(client, projectId) {
1029
1156
  const memberIds = [];
@@ -1065,22 +1192,23 @@ async function ensureMembers(client, projectId) {
1065
1192
  await setMembers(projectId, items);
1066
1193
  return items;
1067
1194
  }
1068
- async function resolveMember(client, projectId, input) {
1195
+ async function buildMemberNameMap(client, projectId) {
1069
1196
  const members = await ensureMembers(client, projectId);
1070
- const byName = members.filter((m) => m.name.includes(input));
1071
- if (byName.length === 1) return byName[0].organizationMemberId;
1072
- if (byName.length > 1) {
1073
- const candidates = byName.map((m) => ` - ${m.name} (${m.organizationMemberId})`).join("\n");
1074
- throw new DoorayCliError(
1075
- `\uBCF5\uC218\uC758 \uBA64\uBC84\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4: "${input}"
1076
- ${candidates}`,
1077
- EXIT_PARAM_ERROR
1078
- );
1197
+ const map = /* @__PURE__ */ new Map();
1198
+ for (const m of members) {
1199
+ if (m.name) map.set(m.organizationMemberId, m.name);
1079
1200
  }
1080
- throw new DoorayCliError(
1081
- `\uBA64\uBC84\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1082
- EXIT_PARAM_ERROR
1201
+ return map;
1202
+ }
1203
+ async function resolveMember(client, projectId, input2) {
1204
+ const members = await ensureMembers(client, projectId);
1205
+ const match = matchByName(
1206
+ members,
1207
+ input2,
1208
+ "\uBA64\uBC84",
1209
+ (m) => `${m.name} (${m.organizationMemberId})`
1083
1210
  );
1211
+ return match.organizationMemberId;
1084
1212
  }
1085
1213
 
1086
1214
  // src/commands/project/members.ts
@@ -1122,14 +1250,18 @@ async function ensureWorkflows(client, projectId) {
1122
1250
  await setWorkflows(projectId, items);
1123
1251
  return items;
1124
1252
  }
1125
- async function resolveWorkflow(client, projectId, input) {
1253
+ async function resolveWorkflow(client, projectId, input2) {
1126
1254
  const workflows = await ensureWorkflows(client, projectId);
1127
- const match = workflows.find((w) => w.name === input || w.class === input);
1128
- if (match) return match.id;
1129
- throw new DoorayCliError(
1130
- `\uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1131
- EXIT_PARAM_ERROR
1132
- );
1255
+ const byClass = workflows.filter((w) => w.class === input2);
1256
+ if (byClass.length === 1) return byClass[0].id;
1257
+ if (byClass.length > 1) {
1258
+ throw new DoorayCliError(
1259
+ `\uB3D9\uC77C class\uC758 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4: "${input2}"`,
1260
+ EXIT_PARAM_ERROR
1261
+ );
1262
+ }
1263
+ const match = matchByName(workflows, input2, "\uC6CC\uD06C\uD50C\uB85C\uC6B0", (w) => `${w.name} [${w.class}] (${w.id})`);
1264
+ return match.id;
1133
1265
  }
1134
1266
 
1135
1267
  // src/commands/project/workflows.ts
@@ -1154,9 +1286,159 @@ var projectWorkflowsCommand = new import_commander7.Command("workflows").descrip
1154
1286
  });
1155
1287
  });
1156
1288
 
1157
- // src/commands/post/list.ts
1289
+ // src/commands/project/groups.ts
1158
1290
  var import_commander8 = require("commander");
1159
1291
 
1292
+ // src/resolvers/member-group.ts
1293
+ async function fetchAllMemberGroups(client, projectId) {
1294
+ const all = [];
1295
+ let page = 0;
1296
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1297
+ while (true) {
1298
+ const res = await client.getProjectMemberGroups(projectId, { page, size });
1299
+ for (const g of res.result) {
1300
+ all.push({ id: g.id, code: g.code });
1301
+ }
1302
+ const total = res.totalCount ?? all.length;
1303
+ if (all.length >= total) break;
1304
+ page++;
1305
+ }
1306
+ return all;
1307
+ }
1308
+ async function ensureMemberGroups(client, projectId) {
1309
+ const entry = await getMemberGroups(projectId);
1310
+ if (entry && !isExpired(entry.updatedAt, MEMBER_GROUPS_TTL_MS)) return entry.data;
1311
+ const items = await fetchAllMemberGroups(client, projectId);
1312
+ await setMemberGroups(projectId, items);
1313
+ return items;
1314
+ }
1315
+
1316
+ // src/commands/project/groups.ts
1317
+ 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) => {
1318
+ const globalOpts = projectGroupsCommand.optsWithGlobals();
1319
+ const config = await getConfigOrThrow();
1320
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1321
+ startSpinner("\uBA64\uBC84 \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C \uC911...");
1322
+ const projectId = await resolveProject(client, project);
1323
+ const groups = await ensureMemberGroups(client, projectId);
1324
+ stopSpinner(true, "\uBA64\uBC84 \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1325
+ output(globalOpts, {
1326
+ headers: ["ID", "Code"],
1327
+ rows: groups.map((g) => [g.id, g.code]),
1328
+ raw: groups,
1329
+ ids: groups.map((g) => g.id)
1330
+ });
1331
+ });
1332
+
1333
+ // src/commands/project/tags.ts
1334
+ var import_commander9 = require("commander");
1335
+
1336
+ // src/resolvers/tag.ts
1337
+ async function fetchAllTags(client, projectId) {
1338
+ const all = [];
1339
+ let page = 0;
1340
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1341
+ while (true) {
1342
+ const res = await client.getProjectTags(projectId, { page, size });
1343
+ for (const t of res.result) {
1344
+ all.push({
1345
+ id: t.id,
1346
+ name: t.name ?? "",
1347
+ color: t.color ?? "",
1348
+ groupId: t.tagGroup?.id ?? null,
1349
+ groupName: t.tagGroup?.name ?? null,
1350
+ groupMandatory: t.tagGroup?.mandatory ?? false,
1351
+ groupSelectOne: t.tagGroup?.selectOne ?? false
1352
+ });
1353
+ }
1354
+ const total = res.totalCount ?? all.length;
1355
+ if (all.length >= total) break;
1356
+ page++;
1357
+ }
1358
+ return all;
1359
+ }
1360
+ async function ensureTags(client, projectId) {
1361
+ const entry = await getTags(projectId);
1362
+ if (entry && !isExpired(entry.updatedAt, TAGS_TTL_MS)) return entry.data;
1363
+ const items = await fetchAllTags(client, projectId);
1364
+ await setTags(projectId, items);
1365
+ return items;
1366
+ }
1367
+ async function resolveTags(client, projectId, inputs) {
1368
+ const allTags = await ensureTags(client, projectId);
1369
+ const selected = inputs.map(
1370
+ (input2) => matchByName(
1371
+ allTags,
1372
+ input2,
1373
+ "\uD0DC\uADF8",
1374
+ (t) => t.groupName ? `${t.groupName} / ${t.name} (${t.id})` : `${t.name} (${t.id})`
1375
+ )
1376
+ );
1377
+ const mandatoryGroups = /* @__PURE__ */ new Map();
1378
+ for (const t of allTags) {
1379
+ if (t.groupMandatory && t.groupId) mandatoryGroups.set(t.groupId, t.groupName ?? t.groupId);
1380
+ }
1381
+ const coveredGroups = new Set(selected.map((t) => t.groupId).filter((g) => !!g));
1382
+ const missing = [];
1383
+ for (const [gid, gname] of mandatoryGroups) {
1384
+ if (!coveredGroups.has(gid)) missing.push(gname);
1385
+ }
1386
+ if (missing.length > 0) {
1387
+ throw new DoorayCliError(
1388
+ `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1389
+ ` + missing.map((g) => ` - ${g}`).join("\n"),
1390
+ EXIT_PARAM_ERROR
1391
+ );
1392
+ }
1393
+ const selectOneGroups = /* @__PURE__ */ new Map();
1394
+ for (const t of selected) {
1395
+ if (!t.groupSelectOne || !t.groupId) continue;
1396
+ const entry = selectOneGroups.get(t.groupId) ?? { name: t.groupName ?? t.groupId, tags: [] };
1397
+ entry.tags.push(t.name);
1398
+ selectOneGroups.set(t.groupId, entry);
1399
+ }
1400
+ const violators = [];
1401
+ for (const [, info] of selectOneGroups) {
1402
+ if (info.tags.length > 1) {
1403
+ violators.push(`${info.name} (\uC120\uD0DD: ${info.tags.join(", ")})`);
1404
+ }
1405
+ }
1406
+ if (violators.length > 0) {
1407
+ throw new DoorayCliError(
1408
+ `\uB2E4\uC74C \uD0DC\uADF8 \uADF8\uB8F9\uC740 1\uAC1C\uB9CC \uC120\uD0DD \uAC00\uB2A5\uD569\uB2C8\uB2E4:
1409
+ ` + violators.map((v) => ` - ${v}`).join("\n"),
1410
+ EXIT_PARAM_ERROR
1411
+ );
1412
+ }
1413
+ return selected.map((t) => t.id);
1414
+ }
1415
+
1416
+ // src/commands/project/tags.ts
1417
+ 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) => {
1418
+ const globalOpts = projectTagsCommand.optsWithGlobals();
1419
+ const config = await getConfigOrThrow();
1420
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1421
+ startSpinner("\uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C \uC911...");
1422
+ const projectId = await resolveProject(client, project);
1423
+ const tags = await ensureTags(client, projectId);
1424
+ stopSpinner(true, "\uD0DC\uADF8 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1425
+ output(globalOpts, {
1426
+ headers: ["ID", "Color", "Name", "Group", "Mandatory"],
1427
+ rows: tags.map((t) => [
1428
+ t.id,
1429
+ t.color,
1430
+ t.name,
1431
+ t.groupName ?? "",
1432
+ t.groupMandatory ? "Y" : ""
1433
+ ]),
1434
+ raw: tags,
1435
+ ids: tags.map((t) => t.id)
1436
+ });
1437
+ });
1438
+
1439
+ // src/commands/post/list.ts
1440
+ var import_commander10 = require("commander");
1441
+
1160
1442
  // src/formatters/post.ts
1161
1443
  function formatPostList(posts, opts) {
1162
1444
  output(opts, {
@@ -1206,7 +1488,7 @@ function formatCommentList(comments, opts) {
1206
1488
  }
1207
1489
 
1208
1490
  // src/commands/post/list.ts
1209
- 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) => {
1491
+ 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) => {
1210
1492
  const globalOpts = postListCommand.optsWithGlobals();
1211
1493
  const config = await getConfigOrThrow();
1212
1494
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1240,8 +1522,8 @@ var postListCommand = new import_commander8.Command("list").description("\uC5C5\
1240
1522
  });
1241
1523
 
1242
1524
  // src/commands/post/search.ts
1243
- var import_commander9 = require("commander");
1244
- 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) => {
1525
+ var import_commander11 = require("commander");
1526
+ 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) => {
1245
1527
  const globalOpts = postSearchCommand.optsWithGlobals();
1246
1528
  const config = await getConfigOrThrow();
1247
1529
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1253,7 +1535,7 @@ var postSearchCommand = new import_commander9.Command("search").description("\uC
1253
1535
  });
1254
1536
 
1255
1537
  // src/commands/post/get.ts
1256
- var import_commander10 = require("commander");
1538
+ var import_commander12 = require("commander");
1257
1539
 
1258
1540
  // src/resolvers/post.ts
1259
1541
  async function resolvePost(client, projectId, postNumber) {
@@ -1269,21 +1551,102 @@ async function resolvePost(client, projectId, postNumber) {
1269
1551
  return res.result[0].id;
1270
1552
  }
1271
1553
 
1554
+ // src/utils/dooray-url.ts
1555
+ var TASK_URL_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/to\/(\d+)(?:[/?#].*)?$/;
1556
+ function parseDoorayTaskUrl(input2) {
1557
+ const m = TASK_URL_RE.exec(input2);
1558
+ return m ? m[1] : null;
1559
+ }
1560
+ function isLikelyDoorayUrl(input2) {
1561
+ return /^https?:\/\//.test(input2);
1562
+ }
1563
+
1564
+ // src/resolvers/post-input.ts
1565
+ var INPUT_HELP = "\uC5C5\uBB34\uB97C \uC2DD\uBCC4\uD560 \uC815\uBCF4\uAC00 \uBD80\uC871\uD569\uB2C8\uB2E4. \uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC785\uB825\uD558\uC138\uC694:\n - <project> <post-number> \uC608: tc-ocr 337\n - --id <postId> \uC608: --id 4319587406666362045\n - <Dooray URL> \uC608: https://x.dooray.com/task/to/4319587406666362045";
1566
+ async function resolveByPostId(client, postId) {
1567
+ const res = await client.getPostStandalone(postId);
1568
+ const d = res.result;
1569
+ return {
1570
+ projectId: d.project.id,
1571
+ projectCode: d.project.code,
1572
+ postId: d.id,
1573
+ postNumber: d.number
1574
+ };
1575
+ }
1576
+ async function resolvePostInput(client, args) {
1577
+ const { projectArg, postNumberArg, idOpt, urlOpt } = args;
1578
+ const hasPositional = !!projectArg || !!postNumberArg;
1579
+ if (idOpt && urlOpt) {
1580
+ throw new DoorayCliError(
1581
+ "--id\uC640 --url\uC740 \uB3D9\uC2DC\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1582
+ EXIT_PARAM_ERROR
1583
+ );
1584
+ }
1585
+ if ((idOpt || urlOpt) && hasPositional) {
1586
+ throw new DoorayCliError(
1587
+ "--id/--url\uACFC positional \uC778\uC790(<project> <post-number>)\uB294 \uB3D9\uC2DC\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1588
+ EXIT_PARAM_ERROR
1589
+ );
1590
+ }
1591
+ if (urlOpt) {
1592
+ const postId = parseDoorayTaskUrl(urlOpt);
1593
+ if (!postId) {
1594
+ throw new DoorayCliError(
1595
+ `--url \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${urlOpt}"
1596
+ \uC608: https://x.dooray.com/task/to/4319587406666362045`,
1597
+ EXIT_PARAM_ERROR
1598
+ );
1599
+ }
1600
+ return resolveByPostId(client, postId);
1601
+ }
1602
+ if (idOpt) {
1603
+ return resolveByPostId(client, idOpt);
1604
+ }
1605
+ if (projectArg && !postNumberArg && isLikelyDoorayUrl(projectArg)) {
1606
+ const postId = parseDoorayTaskUrl(projectArg);
1607
+ if (!postId) {
1608
+ throw new DoorayCliError(
1609
+ `Dooray URL \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${projectArg}"
1610
+ \uC608: https://x.dooray.com/task/to/4319587406666362045`,
1611
+ EXIT_PARAM_ERROR
1612
+ );
1613
+ }
1614
+ return resolveByPostId(client, postId);
1615
+ }
1616
+ if (projectArg && postNumberArg) {
1617
+ const projectId = await resolveProject(client, projectArg);
1618
+ const num = Number(postNumberArg);
1619
+ if (!Number.isFinite(num) || num <= 0) {
1620
+ throw new DoorayCliError(
1621
+ `<post-number>\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${postNumberArg}"`,
1622
+ EXIT_PARAM_ERROR
1623
+ );
1624
+ }
1625
+ const postId = await resolvePost(client, projectId, num);
1626
+ return { projectId, projectCode: projectArg, postId, postNumber: num };
1627
+ }
1628
+ throw new DoorayCliError(INPUT_HELP, EXIT_PARAM_ERROR);
1629
+ }
1630
+
1272
1631
  // src/commands/post/get.ts
1273
- var postGetCommand = new import_commander10.Command("get").description("\uC5C5\uBB34 \uC0C1\uC138 \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").action(async (project, postNumberStr) => {
1632
+ 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) => {
1274
1633
  const globalOpts = postGetCommand.optsWithGlobals();
1275
1634
  const config = await getConfigOrThrow();
1276
1635
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1277
1636
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
1278
- const projectId = await resolveProject(client, project);
1279
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1637
+ const { projectId, postId } = await resolvePostInput(client, {
1638
+ projectArg: project,
1639
+ postNumberArg: postNumberStr,
1640
+ idOpt: opts.id,
1641
+ urlOpt: opts.url
1642
+ });
1280
1643
  const res = await client.getPost(projectId, postId);
1281
1644
  stopSpinner(true, "\uC5C5\uBB34 \uC870\uD68C \uC644\uB8CC");
1282
1645
  formatPostDetail(res.result, globalOpts);
1283
1646
  });
1284
1647
 
1285
1648
  // src/commands/post/edit.ts
1286
- var import_commander11 = require("commander");
1649
+ var import_commander13 = require("commander");
1287
1650
 
1288
1651
  // src/editor/index.ts
1289
1652
  var import_node_child_process = require("child_process");
@@ -1291,8 +1654,8 @@ var import_promises6 = __toESM(require("fs/promises"));
1291
1654
  var import_tmp = __toESM(require("tmp"));
1292
1655
  var import_js_yaml = __toESM(require("js-yaml"));
1293
1656
  function openInEditor(content) {
1294
- const editor = process.env.EDITOR;
1295
- if (!editor) {
1657
+ const editor2 = process.env.EDITOR;
1658
+ if (!editor2) {
1296
1659
  throw new DoorayCliError(
1297
1660
  "$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.",
1298
1661
  EXIT_PARAM_ERROR
@@ -1302,7 +1665,7 @@ function openInEditor(content) {
1302
1665
  return new Promise(async (resolve, reject) => {
1303
1666
  try {
1304
1667
  await import_promises6.default.writeFile(tmpFile.name, content, "utf-8");
1305
- const child = (0, import_node_child_process.spawn)(editor, [tmpFile.name], {
1668
+ const child = (0, import_node_child_process.spawn)(editor2, [tmpFile.name], {
1306
1669
  stdio: "inherit"
1307
1670
  });
1308
1671
  child.on("error", (err) => {
@@ -1453,12 +1816,16 @@ async function resolveUsers(client, projectId, emails) {
1453
1816
  }
1454
1817
  return users;
1455
1818
  }
1456
- 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 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").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) => {
1819
+ 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) => {
1457
1820
  const config = await getConfigOrThrow();
1458
1821
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1459
1822
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
1460
- const projectId = await resolveProject(client, project);
1461
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1823
+ const { projectId, postId, postNumber } = await resolvePostInput(client, {
1824
+ projectArg: project,
1825
+ postNumberArg: postNumberStr,
1826
+ idOpt: opts.id,
1827
+ urlOpt: opts.url
1828
+ });
1462
1829
  const res = await client.getPost(projectId, postId);
1463
1830
  const post = res.result;
1464
1831
  const members = await ensureMembers(client, projectId);
@@ -1518,21 +1885,67 @@ var postEditCommand = new import_commander11.Command("edit").description("\uC5C5
1518
1885
  });
1519
1886
  stopSpinner(true, "\uC5C5\uBB34 \uC218\uC815 \uC644\uB8CC");
1520
1887
  }
1521
- process.stdout.write(`#${postNumberStr} \uC5C5\uBB34\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1888
+ process.stdout.write(`#${postNumber} \uC5C5\uBB34\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1522
1889
  `);
1523
1890
  });
1524
1891
 
1525
1892
  // src/commands/post/create.ts
1526
- var import_commander12 = require("commander");
1893
+ var import_commander14 = require("commander");
1894
+
1895
+ // src/resolvers/milestone.ts
1896
+ async function fetchAllMilestones(client, projectId) {
1897
+ const all = [];
1898
+ let page = 0;
1899
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1900
+ while (true) {
1901
+ const res = await client.getProjectMilestones(projectId, { page, size });
1902
+ for (const m of res.result) all.push({ id: m.id, name: m.name });
1903
+ const total = res.totalCount ?? all.length;
1904
+ if (all.length >= total) break;
1905
+ page++;
1906
+ }
1907
+ return all;
1908
+ }
1909
+ async function ensureMilestones(client, projectId) {
1910
+ const entry = await getMilestones(projectId);
1911
+ if (entry && !isExpired(entry.updatedAt, MILESTONES_TTL_MS)) return entry.data;
1912
+ const items = await fetchAllMilestones(client, projectId);
1913
+ await setMilestones(projectId, items);
1914
+ return items;
1915
+ }
1916
+ async function resolveMilestone(client, projectId, input2) {
1917
+ const all = await ensureMilestones(client, projectId);
1918
+ const match = matchByName(all, input2, "\uB9C8\uC77C\uC2A4\uD1A4", (m) => `${m.name} (${m.id})`);
1919
+ return match.id;
1920
+ }
1921
+
1922
+ // src/resolvers/postRef.ts
1923
+ async function resolvePostRef(client, ref) {
1924
+ if (ref.includes("/")) {
1925
+ const [code, numStr] = ref.split("/", 2);
1926
+ const num = Number(numStr);
1927
+ if (!code || !Number.isFinite(num) || num <= 0) {
1928
+ throw new DoorayCliError(
1929
+ `--parent \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${ref}" (\uC608: "tc-ocr/337" \uB610\uB294 raw postId)`,
1930
+ EXIT_PARAM_ERROR
1931
+ );
1932
+ }
1933
+ const projectId = await resolveProject(client, code);
1934
+ return resolvePost(client, projectId, num);
1935
+ }
1936
+ return ref;
1937
+ }
1938
+
1939
+ // src/commands/post/create.ts
1527
1940
  async function resolveUsers2(client, projectId, inputs) {
1528
1941
  const users = [];
1529
- for (const input of inputs) {
1530
- const memberId = await resolveMember(client, projectId, input);
1942
+ for (const input2 of inputs) {
1943
+ const memberId = await resolveMember(client, projectId, input2);
1531
1944
  users.push({ type: "member", member: { organizationMemberId: memberId } });
1532
1945
  }
1533
1946
  return users;
1534
1947
  }
1535
- 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)").action(async (project, opts) => {
1948
+ 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) => {
1536
1949
  const globalOpts = postCreateCommand.optsWithGlobals();
1537
1950
  const config = await getConfigOrThrow();
1538
1951
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1553,14 +1966,33 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1553
1966
  const projectId = await resolveProject(client, project);
1554
1967
  const toUsers = opts.to ? await resolveUsers2(client, projectId, opts.to) : [];
1555
1968
  const ccUsers = opts.cc ? await resolveUsers2(client, projectId, opts.cc) : [];
1969
+ const tagInputs = (opts.tag ?? []).filter((s) => s.length > 0);
1970
+ const [tagIds, parentPostId, milestoneId] = await Promise.all([
1971
+ tagInputs.length > 0 ? resolveTags(client, projectId, tagInputs) : Promise.resolve(void 0),
1972
+ opts.parent ? resolvePostRef(client, opts.parent) : Promise.resolve(void 0),
1973
+ opts.milestone ? resolveMilestone(client, projectId, opts.milestone) : Promise.resolve(void 0)
1974
+ ]);
1556
1975
  const res = await client.createPost(projectId, {
1557
1976
  subject,
1558
1977
  body: { mimeType: "text/x-markdown", content: bodyContent },
1559
1978
  users: { to: toUsers, cc: ccUsers },
1560
1979
  priority: opts.priority,
1561
- ...opts.dueDate && { dueDate: opts.dueDate, dueDateFlag: true }
1980
+ ...opts.dueDate && { dueDate: opts.dueDate, dueDateFlag: true },
1981
+ ...parentPostId && { parentPostId },
1982
+ ...milestoneId && { milestoneId },
1983
+ ...tagIds && tagIds.length > 0 && { tagIds }
1562
1984
  });
1563
1985
  stopSpinner(true, "\uC5C5\uBB34 \uC0DD\uC131 \uC644\uB8CC");
1986
+ if (opts.workflow) {
1987
+ try {
1988
+ const workflowId = await resolveWorkflow(client, projectId, opts.workflow);
1989
+ await client.setPostWorkflow(projectId, res.result.id, workflowId);
1990
+ } catch (err) {
1991
+ const msg = err instanceof Error ? err.message : String(err);
1992
+ process.stderr.write(`\u26A0 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815 \uC2E4\uD328 (post\uB294 \uC0DD\uC131\uB428): ${msg}
1993
+ `);
1994
+ }
1995
+ }
1564
1996
  if (globalOpts.json) {
1565
1997
  printJson(res.result);
1566
1998
  } else if (globalOpts.quiet) {
@@ -1572,54 +2004,244 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1572
2004
  });
1573
2005
 
1574
2006
  // src/commands/post/done.ts
1575
- var import_commander13 = require("commander");
1576
- var postDoneCommand = new import_commander13.Command("done").description("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").action(async (project, postNumberStr) => {
2007
+ var import_commander15 = require("commander");
2008
+ 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) => {
1577
2009
  const config = await getConfigOrThrow();
1578
2010
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1579
2011
  startSpinner("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC \uC911...");
1580
- const projectId = await resolveProject(client, project);
1581
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2012
+ const { projectId, postId, postNumber } = await resolvePostInput(client, {
2013
+ projectArg: project,
2014
+ postNumberArg: postNumberStr,
2015
+ idOpt: opts.id,
2016
+ urlOpt: opts.url
2017
+ });
1582
2018
  await client.setPostDone(projectId, postId);
1583
2019
  stopSpinner(true, "\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC \uC644\uB8CC");
1584
- process.stdout.write(`#${postNumberStr} \uC5C5\uBB34\uAC00 \uC644\uB8CC \uCC98\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
2020
+ process.stdout.write(`#${postNumber} \uC5C5\uBB34\uAC00 \uC644\uB8CC \uCC98\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1585
2021
  `);
1586
2022
  });
1587
2023
 
1588
2024
  // src/commands/post/workflow.ts
1589
- var import_commander14 = require("commander");
1590
- 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 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<workflow>", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 \uD074\uB798\uC2A4").action(async (project, postNumberStr, workflow) => {
2025
+ var import_commander16 = require("commander");
2026
+ 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) => {
1591
2027
  const config = await getConfigOrThrow();
1592
2028
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2029
+ let workflowInput = opts.workflow;
2030
+ let projectArg = project;
2031
+ let postNumberArg = postNumber;
2032
+ if (!workflowInput) {
2033
+ if (workflowArg) {
2034
+ workflowInput = workflowArg;
2035
+ } else if ((opts.id || opts.url) && projectArg && !postNumberArg) {
2036
+ workflowInput = projectArg;
2037
+ projectArg = void 0;
2038
+ } else if (!opts.id && !opts.url && projectArg && postNumberArg && !workflowArg) {
2039
+ if (isLikelyDoorayUrl(projectArg)) {
2040
+ workflowInput = postNumberArg;
2041
+ postNumberArg = void 0;
2042
+ }
2043
+ }
2044
+ }
2045
+ if (!workflowInput) {
2046
+ throw new DoorayCliError(
2047
+ "workflow\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional \uB610\uB294 --workflow\uB85C \uC785\uB825\uD558\uC138\uC694.",
2048
+ EXIT_PARAM_ERROR
2049
+ );
2050
+ }
1593
2051
  startSpinner("\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD \uC911...");
1594
- const projectId = await resolveProject(client, project);
1595
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1596
- const workflowId = await resolveWorkflow(client, projectId, workflow);
2052
+ const { projectId, postId, postNumber: resolvedPostNumber } = await resolvePostInput(client, {
2053
+ projectArg,
2054
+ postNumberArg,
2055
+ idOpt: opts.id,
2056
+ urlOpt: opts.url
2057
+ });
2058
+ const workflowId = await resolveWorkflow(client, projectId, workflowInput);
1597
2059
  await client.setPostWorkflow(projectId, postId, workflowId);
1598
2060
  stopSpinner(true, "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD \uC644\uB8CC");
1599
- process.stdout.write(`#${postNumberStr} \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 "${workflow}"(\uC73C)\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
2061
+ process.stdout.write(`#${resolvedPostNumber} \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 "${workflowInput}"(\uC73C)\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1600
2062
  `);
1601
2063
  });
1602
2064
 
1603
2065
  // src/commands/post/comment/list.ts
1604
- var import_commander15 = require("commander");
1605
- var commentListCommand = new import_commander15.Command("list").description("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (project, postNumberStr, opts) => {
2066
+ var import_commander17 = require("commander");
2067
+
2068
+ // src/utils/comment-enrich.ts
2069
+ function enrichCommentCreators(comments, nameMap) {
2070
+ return comments.map((c) => {
2071
+ const id = c.creator?.member?.organizationMemberId;
2072
+ const existing = c.creator?.member?.name;
2073
+ if (existing || !id) return c;
2074
+ const filled = nameMap.get(id);
2075
+ if (!filled) return c;
2076
+ return {
2077
+ ...c,
2078
+ creator: {
2079
+ ...c.creator,
2080
+ member: { ...c.creator.member, name: filled }
2081
+ }
2082
+ };
2083
+ });
2084
+ }
2085
+
2086
+ // src/commands/post/comment/list.ts
2087
+ var PAGE_DEFAULT = "0";
2088
+ var SIZE_DEFAULT = "20";
2089
+ function parseSinceDate(input2) {
2090
+ const d = new Date(input2);
2091
+ if (isNaN(d.getTime())) {
2092
+ throw new DoorayCliError(
2093
+ `--since \uAC12\uC744 \uD30C\uC2F1\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: "${input2}" (ISO 8601 \uB610\uB294 YYYY-MM-DD)`,
2094
+ EXIT_PARAM_ERROR
2095
+ );
2096
+ }
2097
+ return d;
2098
+ }
2099
+ function validateExclusive(opts) {
2100
+ function err(msg) {
2101
+ return new DoorayCliError(msg, EXIT_PARAM_ERROR);
2102
+ }
2103
+ if (opts.latest) {
2104
+ if (opts.page && opts.page !== PAGE_DEFAULT) throw err("--latest\uC640 --page\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2105
+ if (opts.size && opts.size !== SIZE_DEFAULT) throw err("--latest\uC640 --size\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2106
+ if (opts.sort && opts.sort !== "asc") throw err("--latest\uC640 --sort\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2107
+ if (opts.reverse) throw err("--latest\uC640 --reverse\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2108
+ if (opts.since) throw err("--latest\uC640 --since\uB294 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2109
+ }
2110
+ if (opts.reverse && opts.sort && opts.sort !== "asc") {
2111
+ throw err("--reverse\uC640 --sort \uC635\uC158\uC740 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00");
2112
+ }
2113
+ if (opts.sort && opts.sort !== "asc" && opts.sort !== "desc") {
2114
+ throw err(`--sort\uB294 asc \uB610\uB294 desc\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4: "${opts.sort}"`);
2115
+ }
2116
+ }
2117
+ function resolveOrder(opts) {
2118
+ if (opts.reverse) return "-createdAt";
2119
+ if (opts.sort === "desc") return "-createdAt";
2120
+ return "createdAt";
2121
+ }
2122
+ async function fetchSince(client, projectId, postId, sinceDate) {
2123
+ const sinceMs = sinceDate.getTime();
2124
+ const collected = [];
2125
+ let page = 0;
2126
+ const size = 100;
2127
+ while (true) {
2128
+ const res = await client.getPostComments(projectId, postId, {
2129
+ page,
2130
+ size,
2131
+ order: "-createdAt"
2132
+ });
2133
+ const pageItems = res.result;
2134
+ const survivors = pageItems.filter((c) => new Date(c.createdAt).getTime() >= sinceMs);
2135
+ collected.push(...survivors);
2136
+ const hasOlder = pageItems.length > survivors.length;
2137
+ if (hasOlder) break;
2138
+ if (pageItems.length < size) break;
2139
+ page++;
2140
+ }
2141
+ return collected;
2142
+ }
2143
+ 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) => {
1606
2144
  const globalOpts = commentListCommand.optsWithGlobals();
1607
2145
  const config = await getConfigOrThrow();
1608
2146
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2147
+ validateExclusive(opts);
2148
+ if (opts.latest) {
2149
+ const n = Number(opts.latest);
2150
+ if (!Number.isFinite(n) || n <= 0) {
2151
+ throw new DoorayCliError("--latest\uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2152
+ }
2153
+ if (n > 100) {
2154
+ throw new DoorayCliError("--latest\uB294 \uCD5C\uB300 100\uAE4C\uC9C0 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2155
+ }
2156
+ }
2157
+ const sinceDate = opts.since ? parseSinceDate(opts.since) : null;
1609
2158
  startSpinner("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC911...");
1610
- const projectId = await resolveProject(client, project);
1611
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1612
- const res = await client.getPostComments(projectId, postId, {
1613
- page: Number(opts.page),
1614
- size: Number(opts.size)
2159
+ const { projectId, postId } = await resolvePostInput(client, {
2160
+ projectArg: project,
2161
+ postNumberArg: postNumberStr,
2162
+ idOpt: opts.id,
2163
+ urlOpt: opts.url
1615
2164
  });
2165
+ const order = resolveOrder(opts);
2166
+ let comments;
2167
+ if (opts.latest) {
2168
+ const n = Number(opts.latest);
2169
+ const res = await client.getPostComments(projectId, postId, {
2170
+ page: 0,
2171
+ size: n,
2172
+ order: "-createdAt"
2173
+ });
2174
+ comments = res.result;
2175
+ } else if (sinceDate) {
2176
+ comments = await fetchSince(client, projectId, postId, sinceDate);
2177
+ if (order === "createdAt") comments = [...comments].reverse();
2178
+ } else {
2179
+ const res = await client.getPostComments(projectId, postId, {
2180
+ page: Number(opts.page),
2181
+ size: Number(opts.size),
2182
+ order
2183
+ });
2184
+ comments = res.result;
2185
+ }
1616
2186
  stopSpinner(true, "\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1617
- formatCommentList(res.result, globalOpts);
2187
+ if (opts.fromAuthor) {
2188
+ const memberId = await resolveMember(client, projectId, opts.fromAuthor);
2189
+ comments = comments.filter(
2190
+ (c) => c.creator?.member?.organizationMemberId === memberId
2191
+ );
2192
+ }
2193
+ if (!globalOpts.json) {
2194
+ let nameMap = /* @__PURE__ */ new Map();
2195
+ try {
2196
+ nameMap = await buildMemberNameMap(client, projectId);
2197
+ } catch {
2198
+ }
2199
+ comments = enrichCommentCreators(comments, nameMap);
2200
+ }
2201
+ formatCommentList(comments, globalOpts);
2202
+ });
2203
+
2204
+ // src/commands/post/comment/latest.ts
2205
+ var import_commander18 = require("commander");
2206
+ 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) => {
2207
+ const globalOpts = commentLatestCommand.optsWithGlobals();
2208
+ const config = await getConfigOrThrow();
2209
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2210
+ const n = Number(opts.count);
2211
+ if (!Number.isFinite(n) || n <= 0) {
2212
+ throw new DoorayCliError("--count\uB294 \uC591\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2213
+ }
2214
+ if (n > 100) {
2215
+ throw new DoorayCliError("--count\uB294 \uCD5C\uB300 100\uAE4C\uC9C0 \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2216
+ }
2217
+ startSpinner("\uCD5C\uC2E0 \uB313\uAE00 \uC870\uD68C \uC911...");
2218
+ const { projectId, postId } = await resolvePostInput(client, {
2219
+ projectArg: project,
2220
+ postNumberArg: postNumberStr,
2221
+ idOpt: opts.id,
2222
+ urlOpt: opts.url
2223
+ });
2224
+ const res = await client.getPostComments(projectId, postId, {
2225
+ page: 0,
2226
+ size: Math.min(n, 100),
2227
+ order: "-createdAt"
2228
+ });
2229
+ stopSpinner(true, "\uC870\uD68C \uC644\uB8CC");
2230
+ let comments = res.result.slice(0, n);
2231
+ if (!globalOpts.json) {
2232
+ let nameMap = /* @__PURE__ */ new Map();
2233
+ try {
2234
+ nameMap = await buildMemberNameMap(client, projectId);
2235
+ } catch {
2236
+ }
2237
+ comments = enrichCommentCreators(comments, nameMap);
2238
+ }
2239
+ formatCommentList(comments, globalOpts);
1618
2240
  });
1619
2241
 
1620
2242
  // src/commands/post/comment/add.ts
1621
- var import_commander16 = require("commander");
1622
- var commentAddCommand = new import_commander16.Command("add").description("\uB313\uAE00 \uCD94\uAC00").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").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) => {
2243
+ var import_commander19 = require("commander");
2244
+ 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)").action(async (project, postNumberStr, opts) => {
1623
2245
  const globalOpts = commentAddCommand.optsWithGlobals();
1624
2246
  const config = await getConfigOrThrow();
1625
2247
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1632,8 +2254,12 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
1632
2254
  }
1633
2255
  }
1634
2256
  startSpinner("\uB313\uAE00 \uCD94\uAC00 \uC911...");
1635
- const projectId = await resolveProject(client, project);
1636
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2257
+ const { projectId, postId } = await resolvePostInput(client, {
2258
+ projectArg: project,
2259
+ postNumberArg: postNumberStr,
2260
+ idOpt: opts.id,
2261
+ urlOpt: opts.url
2262
+ });
1637
2263
  const res = await client.createPostComment(projectId, postId, {
1638
2264
  body: { mimeType: "text/x-markdown", content: bodyContent }
1639
2265
  });
@@ -1649,13 +2275,56 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
1649
2275
  });
1650
2276
 
1651
2277
  // src/commands/post/comment/edit.ts
1652
- var import_commander17 = require("commander");
1653
- var commentEditCommand = new import_commander17.Command("edit").description("\uB313\uAE00 \uC218\uC815 ($EDITOR \uB610\uB294 --body \uC635\uC158)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<comment-id>", "\uB313\uAE00 ID").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 (project, postNumberStr, commentId, opts) => {
2278
+ var import_commander20 = require("commander");
2279
+ 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)").action(async (arg1, arg2, arg3, opts) => {
1654
2280
  const config = await getConfigOrThrow();
1655
2281
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2282
+ let projectArg;
2283
+ let postNumberArg;
2284
+ let commentId = opts.commentId;
2285
+ if (opts.id || opts.url) {
2286
+ if (arg2 || arg3) {
2287
+ throw new DoorayCliError(
2288
+ "--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.",
2289
+ EXIT_PARAM_ERROR
2290
+ );
2291
+ }
2292
+ commentId = commentId ?? arg1;
2293
+ } else if (arg3) {
2294
+ projectArg = arg1;
2295
+ postNumberArg = arg2;
2296
+ commentId = commentId ?? arg3;
2297
+ } else if (arg1 && !arg2) {
2298
+ projectArg = arg1;
2299
+ if (!commentId) {
2300
+ throw new DoorayCliError(
2301
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --comment-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2302
+ EXIT_PARAM_ERROR
2303
+ );
2304
+ }
2305
+ } else {
2306
+ projectArg = arg1;
2307
+ postNumberArg = arg2;
2308
+ if (!commentId) {
2309
+ throw new DoorayCliError(
2310
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2311
+ EXIT_PARAM_ERROR
2312
+ );
2313
+ }
2314
+ }
2315
+ if (!commentId) {
2316
+ throw new DoorayCliError(
2317
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2318
+ EXIT_PARAM_ERROR
2319
+ );
2320
+ }
1656
2321
  startSpinner("\uB313\uAE00 \uC870\uD68C \uC911...");
1657
- const projectId = await resolveProject(client, project);
1658
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2322
+ const { projectId, postId } = await resolvePostInput(client, {
2323
+ projectArg,
2324
+ postNumberArg,
2325
+ idOpt: opts.id,
2326
+ urlOpt: opts.url
2327
+ });
1659
2328
  const comments = await client.getPostComments(projectId, postId);
1660
2329
  const comment = comments.result.find((c) => c.id === commentId);
1661
2330
  stopSpinner(true, "\uB313\uAE00 \uC870\uD68C \uC644\uB8CC");
@@ -1683,13 +2352,56 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
1683
2352
  });
1684
2353
 
1685
2354
  // src/commands/post/comment/delete.ts
1686
- var import_commander18 = require("commander");
1687
- var commentDeleteCommand = new import_commander18.Command("delete").description("\uB313\uAE00 \uC0AD\uC81C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<comment-id>", "\uB313\uAE00 ID").action(async (project, postNumberStr, commentId) => {
2355
+ var import_commander21 = require("commander");
2356
+ 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) => {
1688
2357
  const config = await getConfigOrThrow();
1689
2358
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2359
+ let projectArg;
2360
+ let postNumberArg;
2361
+ let commentId = opts.commentId;
2362
+ if (opts.id || opts.url) {
2363
+ if (arg2 || arg3) {
2364
+ throw new DoorayCliError(
2365
+ "--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.",
2366
+ EXIT_PARAM_ERROR
2367
+ );
2368
+ }
2369
+ commentId = commentId ?? arg1;
2370
+ } else if (arg3) {
2371
+ projectArg = arg1;
2372
+ postNumberArg = arg2;
2373
+ commentId = commentId ?? arg3;
2374
+ } else if (arg1 && !arg2) {
2375
+ projectArg = arg1;
2376
+ if (!commentId) {
2377
+ throw new DoorayCliError(
2378
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --comment-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2379
+ EXIT_PARAM_ERROR
2380
+ );
2381
+ }
2382
+ } else {
2383
+ projectArg = arg1;
2384
+ postNumberArg = arg2;
2385
+ if (!commentId) {
2386
+ throw new DoorayCliError(
2387
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2388
+ EXIT_PARAM_ERROR
2389
+ );
2390
+ }
2391
+ }
2392
+ if (!commentId) {
2393
+ throw new DoorayCliError(
2394
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2395
+ EXIT_PARAM_ERROR
2396
+ );
2397
+ }
1690
2398
  startSpinner("\uB313\uAE00 \uC0AD\uC81C \uC911...");
1691
- const projectId = await resolveProject(client, project);
1692
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2399
+ const { projectId, postId } = await resolvePostInput(client, {
2400
+ projectArg,
2401
+ postNumberArg,
2402
+ idOpt: opts.id,
2403
+ urlOpt: opts.url
2404
+ });
1693
2405
  await client.deletePostComment(projectId, postId, commentId);
1694
2406
  stopSpinner(true, "\uB313\uAE00 \uC0AD\uC81C \uC644\uB8CC");
1695
2407
  process.stdout.write(`\uB313\uAE00\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${commentId}
@@ -1697,19 +2409,23 @@ var commentDeleteCommand = new import_commander18.Command("delete").description(
1697
2409
  });
1698
2410
 
1699
2411
  // src/commands/post/file/list.ts
1700
- var import_commander19 = require("commander");
2412
+ var import_commander22 = require("commander");
1701
2413
  function formatSize(bytes) {
1702
2414
  if (bytes < 1024) return `${bytes}B`;
1703
2415
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
1704
2416
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1705
2417
  }
1706
- 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 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").action(async (project, postNumberStr) => {
2418
+ var fileListCommand = new import_commander22.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) => {
1707
2419
  const globalOpts = fileListCommand.optsWithGlobals();
1708
2420
  const config = await getConfigOrThrow();
1709
2421
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1710
2422
  startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
1711
- const projectId = await resolveProject(client, project);
1712
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2423
+ const { projectId, postId } = await resolvePostInput(client, {
2424
+ projectArg: project,
2425
+ postNumberArg: postNumberStr,
2426
+ idOpt: opts.id,
2427
+ urlOpt: opts.url
2428
+ });
1713
2429
  const res = await client.getPostFiles(projectId, postId);
1714
2430
  stopSpinner(true, `\uCCA8\uBD80\uD30C\uC77C ${res.result.length}\uAC1C`);
1715
2431
  output(globalOpts, {
@@ -1727,15 +2443,55 @@ var fileListCommand = new import_commander19.Command("list").description("\uC5C5
1727
2443
  });
1728
2444
 
1729
2445
  // src/commands/post/file/download.ts
1730
- var import_commander20 = require("commander");
2446
+ var import_commander23 = require("commander");
1731
2447
  var import_promises8 = require("fs/promises");
1732
2448
  var import_node_path4 = require("path");
1733
- var fileDownloadCommand = new import_commander20.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-id>", "\uD30C\uC77C ID").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, fileId, opts) => {
2449
+ var fileDownloadCommand = new import_commander23.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) => {
1734
2450
  const config = await getConfigOrThrow();
1735
2451
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2452
+ let projectArg;
2453
+ let postNumberArg;
2454
+ let fileId = opts.fileId;
2455
+ if (opts.id || opts.url) {
2456
+ if (arg2 || arg3) {
2457
+ throw new DoorayCliError(
2458
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uD30C\uC77C ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. --file-id \uC635\uC158 \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
2459
+ EXIT_PARAM_ERROR
2460
+ );
2461
+ }
2462
+ fileId = fileId ?? arg1;
2463
+ } else if (arg3) {
2464
+ projectArg = arg1;
2465
+ postNumberArg = arg2;
2466
+ fileId = fileId ?? arg3;
2467
+ } else if (arg1 && !arg2) {
2468
+ projectArg = arg1;
2469
+ if (!fileId) {
2470
+ throw new DoorayCliError(
2471
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --file-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2472
+ EXIT_PARAM_ERROR
2473
+ );
2474
+ }
2475
+ } else {
2476
+ projectArg = arg1;
2477
+ postNumberArg = arg2;
2478
+ if (!fileId) {
2479
+ throw new DoorayCliError(
2480
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2481
+ EXIT_PARAM_ERROR
2482
+ );
2483
+ }
2484
+ }
2485
+ if (!fileId) {
2486
+ throw new DoorayCliError("\uD30C\uC77C ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2487
+ }
1736
2488
  startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1737
- const projectId = await resolveProject(client, project);
1738
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2489
+ const { projectId, postId } = await resolvePostInput(client, {
2490
+ projectArg,
2491
+ postNumberArg,
2492
+ idOpt: opts.id,
2493
+ urlOpt: opts.url
2494
+ });
1739
2495
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
1740
2496
  const outputPath = (0, import_node_path4.join)(opts.output, fileName);
1741
2497
  await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
@@ -1745,15 +2501,19 @@ var fileDownloadCommand = new import_commander20.Command("download").description
1745
2501
  });
1746
2502
 
1747
2503
  // src/commands/post/file/download-all.ts
1748
- var import_commander21 = require("commander");
2504
+ var import_commander24 = require("commander");
1749
2505
  var import_promises9 = require("fs/promises");
1750
2506
  var import_node_path5 = require("path");
1751
- 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 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
2507
+ var fileDownloadAllCommand = new import_commander24.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) => {
1752
2508
  const config = await getConfigOrThrow();
1753
2509
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1754
2510
  const spinner = startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
1755
- const projectId = await resolveProject(client, project);
1756
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2511
+ const { projectId, postId } = await resolvePostInput(client, {
2512
+ projectArg: project,
2513
+ postNumberArg: postNumberStr,
2514
+ idOpt: opts.id,
2515
+ urlOpt: opts.url
2516
+ });
1757
2517
  const res = await client.getPostFiles(projectId, postId);
1758
2518
  if (res.result.length === 0) {
1759
2519
  stopSpinner(true, "\uCCA8\uBD80\uD30C\uC77C \uC5C6\uC74C");
@@ -1777,15 +2537,55 @@ var fileDownloadAllCommand = new import_commander21.Command("download-all").desc
1777
2537
  });
1778
2538
 
1779
2539
  // src/commands/post/file/upload.ts
1780
- var import_commander22 = require("commander");
2540
+ var import_commander25 = require("commander");
1781
2541
  var import_node_path6 = require("path");
1782
- var fileUploadCommand = new import_commander22.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C").action(async (project, postNumberStr, filePath) => {
2542
+ var fileUploadCommand = new import_commander25.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) => {
1783
2543
  const globalOpts = fileUploadCommand.optsWithGlobals();
1784
2544
  const config = await getConfigOrThrow();
1785
2545
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2546
+ let projectArg;
2547
+ let postNumberArg;
2548
+ let filePath = opts.file;
2549
+ if (opts.id || opts.url) {
2550
+ if (arg2 || arg3) {
2551
+ throw new DoorayCliError(
2552
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uD30C\uC77C \uACBD\uB85C \uC678 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. --file \uC635\uC158 \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
2553
+ EXIT_PARAM_ERROR
2554
+ );
2555
+ }
2556
+ filePath = filePath ?? arg1;
2557
+ } else if (arg3) {
2558
+ projectArg = arg1;
2559
+ postNumberArg = arg2;
2560
+ filePath = filePath ?? arg3;
2561
+ } else if (arg1 && !arg2) {
2562
+ projectArg = arg1;
2563
+ if (!filePath) {
2564
+ throw new DoorayCliError(
2565
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --file \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2566
+ EXIT_PARAM_ERROR
2567
+ );
2568
+ }
2569
+ } else {
2570
+ projectArg = arg1;
2571
+ postNumberArg = arg2;
2572
+ if (!filePath) {
2573
+ throw new DoorayCliError(
2574
+ "<file-path>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --file \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2575
+ EXIT_PARAM_ERROR
2576
+ );
2577
+ }
2578
+ }
2579
+ if (!filePath) {
2580
+ throw new DoorayCliError("\uD30C\uC77C \uACBD\uB85C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2581
+ }
1786
2582
  startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
1787
- const projectId = await resolveProject(client, project);
1788
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2583
+ const { projectId, postId } = await resolvePostInput(client, {
2584
+ projectArg,
2585
+ postNumberArg,
2586
+ idOpt: opts.id,
2587
+ urlOpt: opts.url
2588
+ });
1789
2589
  const res = await client.uploadPostFile(projectId, postId, filePath);
1790
2590
  stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
1791
2591
  if (globalOpts.json) {
@@ -1800,13 +2600,53 @@ var fileUploadCommand = new import_commander22.Command("upload").description("\u
1800
2600
  });
1801
2601
 
1802
2602
  // src/commands/post/file/delete.ts
1803
- var import_commander23 = require("commander");
1804
- var fileDeleteCommand = new import_commander23.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").argument("<file-id>", "\uD30C\uC77C ID").action(async (project, postNumberStr, fileId) => {
2603
+ var import_commander26 = require("commander");
2604
+ var fileDeleteCommand = new import_commander26.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) => {
1805
2605
  const config = await getConfigOrThrow();
1806
2606
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2607
+ let projectArg;
2608
+ let postNumberArg;
2609
+ let fileId = opts.fileId;
2610
+ if (opts.id || opts.url) {
2611
+ if (arg2 || arg3) {
2612
+ throw new DoorayCliError(
2613
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uD30C\uC77C ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. --file-id \uC635\uC158 \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
2614
+ EXIT_PARAM_ERROR
2615
+ );
2616
+ }
2617
+ fileId = fileId ?? arg1;
2618
+ } else if (arg3) {
2619
+ projectArg = arg1;
2620
+ postNumberArg = arg2;
2621
+ fileId = fileId ?? arg3;
2622
+ } else if (arg1 && !arg2) {
2623
+ projectArg = arg1;
2624
+ if (!fileId) {
2625
+ throw new DoorayCliError(
2626
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --file-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2627
+ EXIT_PARAM_ERROR
2628
+ );
2629
+ }
2630
+ } else {
2631
+ projectArg = arg1;
2632
+ postNumberArg = arg2;
2633
+ if (!fileId) {
2634
+ throw new DoorayCliError(
2635
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2636
+ EXIT_PARAM_ERROR
2637
+ );
2638
+ }
2639
+ }
2640
+ if (!fileId) {
2641
+ throw new DoorayCliError("\uD30C\uC77C ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2642
+ }
1807
2643
  startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
1808
- const projectId = await resolveProject(client, project);
1809
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2644
+ const { projectId, postId } = await resolvePostInput(client, {
2645
+ projectArg,
2646
+ postNumberArg,
2647
+ idOpt: opts.id,
2648
+ urlOpt: opts.url
2649
+ });
1810
2650
  await client.deletePostFile(projectId, postId, fileId);
1811
2651
  stopSpinner(true, "\uC0AD\uC81C \uC644\uB8CC");
1812
2652
  process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
@@ -1814,7 +2654,7 @@ var fileDeleteCommand = new import_commander23.Command("delete").description("\u
1814
2654
  });
1815
2655
 
1816
2656
  // src/commands/wiki/list.ts
1817
- var import_commander24 = require("commander");
2657
+ var import_commander27 = require("commander");
1818
2658
 
1819
2659
  // src/formatters/wiki.ts
1820
2660
  function formatWikiList(wikis, opts) {
@@ -1857,7 +2697,7 @@ function formatWikiPageDetail(page, opts) {
1857
2697
  }
1858
2698
 
1859
2699
  // src/commands/wiki/list.ts
1860
- 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) => {
2700
+ var wikiListCommand = new import_commander27.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) => {
1861
2701
  const globalOpts = wikiListCommand.optsWithGlobals();
1862
2702
  const config = await getConfigOrThrow();
1863
2703
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1871,7 +2711,7 @@ var wikiListCommand = new import_commander24.Command("list").description("\uC704
1871
2711
  });
1872
2712
 
1873
2713
  // src/commands/wiki/pages.ts
1874
- var import_commander25 = require("commander");
2714
+ var import_commander28 = require("commander");
1875
2715
 
1876
2716
  // src/resolvers/wiki.ts
1877
2717
  async function resolveWiki(client, projectCode) {
@@ -1915,7 +2755,7 @@ async function resolveWikiHomePageId(client, wikiId) {
1915
2755
  }
1916
2756
 
1917
2757
  // src/commands/wiki/pages.ts
1918
- 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) => {
2758
+ var wikiPagesCommand = new import_commander28.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) => {
1919
2759
  const globalOpts = wikiPagesCommand.optsWithGlobals();
1920
2760
  const config = await getConfigOrThrow();
1921
2761
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1927,8 +2767,8 @@ var wikiPagesCommand = new import_commander25.Command("pages").description("\uC7
1927
2767
  });
1928
2768
 
1929
2769
  // src/commands/wiki/page-get.ts
1930
- var import_commander26 = require("commander");
1931
- 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) => {
2770
+ var import_commander29 = require("commander");
2771
+ var wikiPageGetCommand = new import_commander29.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) => {
1932
2772
  const globalOpts = wikiPageGetCommand.optsWithGlobals();
1933
2773
  const config = await getConfigOrThrow();
1934
2774
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1940,8 +2780,8 @@ var wikiPageGetCommand = new import_commander26.Command("get").description("\uC7
1940
2780
  });
1941
2781
 
1942
2782
  // src/commands/wiki/page-create.ts
1943
- var import_commander27 = require("commander");
1944
- 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) => {
2783
+ var import_commander30 = require("commander");
2784
+ var wikiPageCreateCommand = new import_commander30.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) => {
1945
2785
  const globalOpts = wikiPageCreateCommand.optsWithGlobals();
1946
2786
  const config = await getConfigOrThrow();
1947
2787
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1966,8 +2806,8 @@ var wikiPageCreateCommand = new import_commander27.Command("create").description
1966
2806
  });
1967
2807
 
1968
2808
  // src/commands/wiki/page-edit.ts
1969
- var import_commander28 = require("commander");
1970
- 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) => {
2809
+ var import_commander31 = require("commander");
2810
+ var wikiPageEditCommand = new import_commander31.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) => {
1971
2811
  const config = await getConfigOrThrow();
1972
2812
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1973
2813
  const hasTitle = opts.title != null;
@@ -2019,8 +2859,73 @@ var wikiPageEditCommand = new import_commander28.Command("edit").description("\u
2019
2859
  `);
2020
2860
  });
2021
2861
 
2862
+ // src/commands/member/index.ts
2863
+ var import_commander34 = require("commander");
2864
+
2865
+ // src/commands/member/get.ts
2866
+ var import_commander32 = require("commander");
2867
+
2868
+ // src/formatters/member.ts
2869
+ function formatMemberDetail(member, opts) {
2870
+ if (opts.json) {
2871
+ printJson(member);
2872
+ return;
2873
+ }
2874
+ if (opts.quiet) {
2875
+ process.stdout.write(member.id + "\n");
2876
+ return;
2877
+ }
2878
+ const lines = [
2879
+ `\uC774\uB984: ${member.name}`,
2880
+ ...member.englishName ? [`\uC601\uBB38\uBA85: ${member.englishName}`] : [],
2881
+ ...member.nickname ? [`\uBCC4\uBA85: ${member.nickname}`] : [],
2882
+ ...member.userCode ? [`\uC0AC\uBC88/ID: ${member.userCode}`] : [],
2883
+ ...member.externalEmailAddress ? [`\uC678\uBD80 \uC774\uBA54\uC77C: ${member.externalEmailAddress}`] : [],
2884
+ `member-id: ${member.id}`
2885
+ ];
2886
+ process.stdout.write(lines.join("\n") + "\n");
2887
+ }
2888
+ function formatMemberList(rows, opts) {
2889
+ output(opts, {
2890
+ headers: ["ID", "Name", "Role"],
2891
+ rows: rows.map((r) => [r.id, r.name, r.role ?? ""]),
2892
+ raw: rows,
2893
+ ids: rows.map((r) => r.id)
2894
+ });
2895
+ }
2896
+
2897
+ // src/commands/member/get.ts
2898
+ var memberGetCommand = new import_commander32.Command("get").description("\uBA64\uBC84 \uC0C1\uC138 \uC815\uBCF4 \uC870\uD68C (organizationMemberId)").argument("<member-id>", "\uC870\uD68C\uD560 organization member ID").action(async (memberId) => {
2899
+ const globalOpts = memberGetCommand.optsWithGlobals();
2900
+ const config = await getConfigOrThrow();
2901
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2902
+ const res = await client.getMemberDetail(memberId);
2903
+ formatMemberDetail(res.result, globalOpts);
2904
+ });
2905
+
2906
+ // src/commands/member/list.ts
2907
+ var import_commander33 = require("commander");
2908
+ var memberListCommand = new import_commander33.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) => {
2909
+ const globalOpts = memberListCommand.optsWithGlobals();
2910
+ const config = await getConfigOrThrow();
2911
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2912
+ startSpinner("\uBA64\uBC84 \uBAA9\uB85D \uC870\uD68C \uC911...");
2913
+ const projectId = await resolveProject(client, project);
2914
+ const members = await ensureMembers(client, projectId);
2915
+ stopSpinner(true, `${members.length}\uBA85`);
2916
+ formatMemberList(
2917
+ members.map((m) => ({ id: m.organizationMemberId, name: m.name })),
2918
+ globalOpts
2919
+ );
2920
+ });
2921
+
2922
+ // src/commands/member/index.ts
2923
+ var memberCommand = new import_commander34.Command("member").description("Dooray \uBA64\uBC84 \uC870\uD68C");
2924
+ memberCommand.addCommand(memberGetCommand);
2925
+ memberCommand.addCommand(memberListCommand);
2926
+
2022
2927
  // src/commands/mail/list.ts
2023
- var import_commander29 = require("commander");
2928
+ var import_commander35 = require("commander");
2024
2929
 
2025
2930
  // src/api/imapClient.ts
2026
2931
  var import_imapflow = require("imapflow");
@@ -2128,7 +3033,7 @@ async function getMail(config, uid) {
2128
3033
 
2129
3034
  // src/commands/mail/list.ts
2130
3035
  var import_chalk5 = __toESM(require("chalk"));
2131
- var mailListCommand = new import_commander29.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) => {
3036
+ var mailListCommand = new import_commander35.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) => {
2132
3037
  const globalOpts = mailListCommand.optsWithGlobals();
2133
3038
  const config = await getConfigOrThrow();
2134
3039
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2160,9 +3065,9 @@ var mailListCommand = new import_commander29.Command("list").description("\uBA54
2160
3065
  });
2161
3066
 
2162
3067
  // src/commands/mail/get.ts
2163
- var import_commander30 = require("commander");
3068
+ var import_commander36 = require("commander");
2164
3069
  var import_chalk6 = __toESM(require("chalk"));
2165
- var mailGetCommand = new import_commander30.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
3070
+ var mailGetCommand = new import_commander36.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
2166
3071
  const globalOpts = mailGetCommand.optsWithGlobals();
2167
3072
  const config = await getConfigOrThrow();
2168
3073
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2193,7 +3098,7 @@ ${mail.body}
2193
3098
  });
2194
3099
 
2195
3100
  // src/commands/mail/send.ts
2196
- var import_commander31 = require("commander");
3101
+ var import_commander37 = require("commander");
2197
3102
  var import_promises10 = require("fs/promises");
2198
3103
 
2199
3104
  // src/api/smtpClient.ts
@@ -2243,7 +3148,7 @@ async function sendMail(config, opts) {
2243
3148
  }
2244
3149
 
2245
3150
  // src/commands/mail/send.ts
2246
- var mailSendCommand = new import_commander31.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) => {
3151
+ var mailSendCommand = new import_commander37.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) => {
2247
3152
  const globalOpts = mailSendCommand.optsWithGlobals();
2248
3153
  const config = await getConfigOrThrow();
2249
3154
  let body = opts.body ?? "";
@@ -2278,7 +3183,7 @@ var mailSendCommand = new import_commander31.Command("send").description("\uBA54
2278
3183
  });
2279
3184
 
2280
3185
  // src/commands/mail/reply.ts
2281
- var import_commander32 = require("commander");
3186
+ var import_commander38 = require("commander");
2282
3187
  var import_promises11 = require("fs/promises");
2283
3188
  var import_imapflow2 = require("imapflow");
2284
3189
  async function getMessageId(config, uid) {
@@ -2306,7 +3211,7 @@ async function getMessageId(config, uid) {
2306
3211
  await client.logout();
2307
3212
  }
2308
3213
  }
2309
- var mailReplyCommand = new import_commander32.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) => {
3214
+ var mailReplyCommand = new import_commander38.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) => {
2310
3215
  const globalOpts = mailReplyCommand.optsWithGlobals();
2311
3216
  const config = await getConfigOrThrow();
2312
3217
  let body = opts.body ?? "";
@@ -2346,20 +3251,201 @@ var mailReplyCommand = new import_commander32.Command("reply").description("\uBA
2346
3251
  }
2347
3252
  });
2348
3253
 
3254
+ // src/commands/feedback.ts
3255
+ var import_commander39 = require("commander");
3256
+ var import_node_child_process2 = require("child_process");
3257
+ var import_node_util = require("util");
3258
+ var import_promises13 = require("fs/promises");
3259
+ var import_node_os3 = require("os");
3260
+ var import_node_path8 = require("path");
3261
+ var import_node_crypto = require("crypto");
3262
+ var import_prompts = require("@inquirer/prompts");
3263
+
3264
+ // src/utils/feedback-meta.ts
3265
+ var import_promises12 = require("fs/promises");
3266
+ var import_node_path7 = require("path");
3267
+ async function readCliVersion() {
3268
+ const entry = process.argv[1];
3269
+ const here = entry ? (0, import_node_path7.dirname)(entry) : process.cwd();
3270
+ const candidates = [
3271
+ (0, import_node_path7.join)(here, "package.json"),
3272
+ (0, import_node_path7.join)(here, "..", "package.json"),
3273
+ (0, import_node_path7.join)(here, "..", "..", "package.json")
3274
+ ];
3275
+ for (const p of candidates) {
3276
+ try {
3277
+ const raw = await (0, import_promises12.readFile)(p, "utf-8");
3278
+ const pkg = JSON.parse(raw);
3279
+ if (pkg.name === "@bifos/dooray-cli" && typeof pkg.version === "string") {
3280
+ return pkg.version;
3281
+ }
3282
+ } catch {
3283
+ }
3284
+ }
3285
+ return "unknown";
3286
+ }
3287
+ function collectMeta(version) {
3288
+ return {
3289
+ cliVersion: version,
3290
+ nodeVersion: process.version,
3291
+ os: process.platform,
3292
+ arch: process.arch
3293
+ };
3294
+ }
3295
+ function buildIssueBody(userBody, meta) {
3296
+ return [
3297
+ "## \uD658\uACBD",
3298
+ `- dooray-cli \uBC84\uC804: ${meta.cliVersion}`,
3299
+ `- Node: ${meta.nodeVersion}`,
3300
+ `- OS: ${meta.os} ${meta.arch}`,
3301
+ "",
3302
+ "## \uC0AC\uC6A9\uC790 \uD53C\uB4DC\uBC31",
3303
+ "",
3304
+ userBody.trim(),
3305
+ ""
3306
+ ].join("\n");
3307
+ }
3308
+
3309
+ // src/commands/feedback.ts
3310
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
3311
+ var TARGET_REPO = "jon890/dooray-cli";
3312
+ async function ensureGhInstalled() {
3313
+ try {
3314
+ await execFileAsync("gh", ["--version"]);
3315
+ } catch {
3316
+ throw new DoorayCliError(
3317
+ "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",
3318
+ EXIT_PARAM_ERROR
3319
+ );
3320
+ }
3321
+ }
3322
+ async function readBody(opts) {
3323
+ if (opts.body) return opts.body;
3324
+ if (opts.bodyFile) return await (0, import_promises13.readFile)(opts.bodyFile, "utf-8");
3325
+ return void 0;
3326
+ }
3327
+ var feedbackCommand = new import_commander39.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(
3328
+ "--label <name>",
3329
+ "\uB77C\uBCA8 (\uBC18\uBCF5 \uAC00\uB2A5)",
3330
+ (value, prev) => [...prev, value],
3331
+ []
3332
+ ).option("--dry-run", "gh \uD638\uCD9C \uC5C6\uC774 \uBCF8\uBB38\uB9CC \uBBF8\uB9AC\uBCF4\uAE30").action(async (opts) => {
3333
+ let title = opts.title;
3334
+ let userBody = await readBody(opts);
3335
+ let labels = [...opts.label];
3336
+ if (!title) {
3337
+ title = await (0, import_prompts.input)({ message: "\uC774\uC288 \uC81C\uBAA9" });
3338
+ }
3339
+ if (!title || !title.trim()) {
3340
+ throw new DoorayCliError("\uC81C\uBAA9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
3341
+ }
3342
+ if (labels.length === 0 && !opts.title && !opts.body && !opts.bodyFile) {
3343
+ const labelInput = await (0, import_prompts.input)({
3344
+ message: "\uB77C\uBCA8 (\uCF64\uB9C8\uB85C \uC5EC\uB7EC \uAC1C, \uBE44\uC6B0\uBA74 \uC5C6\uC74C)",
3345
+ default: ""
3346
+ });
3347
+ labels = labelInput.split(",").map((s) => s.trim()).filter(Boolean);
3348
+ }
3349
+ if (userBody == null) {
3350
+ userBody = await (0, import_prompts.editor)({
3351
+ message: "\uBCF8\uBB38 \uC791\uC131 ($EDITOR\uAC00 \uC5F4\uB9BC)",
3352
+ default: ""
3353
+ });
3354
+ }
3355
+ if (!userBody.trim()) {
3356
+ throw new DoorayCliError("\uBCF8\uBB38\uC774 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
3357
+ }
3358
+ const version = await readCliVersion();
3359
+ const meta = collectMeta(version);
3360
+ const issueBody = buildIssueBody(userBody, meta);
3361
+ if (opts.dryRun) {
3362
+ process.stdout.write("--- DRY RUN ---\n");
3363
+ process.stdout.write(`Repo: ${TARGET_REPO}
3364
+ `);
3365
+ process.stdout.write(`Title: ${title}
3366
+ `);
3367
+ process.stdout.write(
3368
+ `Labels: ${labels.length > 0 ? labels.join(", ") : "(\uC5C6\uC74C)"}
3369
+ `
3370
+ );
3371
+ process.stdout.write("Body:\n");
3372
+ process.stdout.write(issueBody);
3373
+ process.stdout.write("--- END ---\n");
3374
+ return;
3375
+ }
3376
+ const isInteractive = !opts.title;
3377
+ if (isInteractive) {
3378
+ process.stderr.write("\n--- \uBBF8\uB9AC\uBCF4\uAE30 ---\n");
3379
+ process.stderr.write(`Repo: ${TARGET_REPO}
3380
+ `);
3381
+ process.stderr.write(`Title: ${title}
3382
+ `);
3383
+ process.stderr.write(
3384
+ `Labels: ${labels.length > 0 ? labels.join(", ") : "(\uC5C6\uC74C)"}
3385
+ `
3386
+ );
3387
+ process.stderr.write("Body:\n");
3388
+ process.stderr.write(issueBody);
3389
+ process.stderr.write("--- \uB05D ---\n\n");
3390
+ const ok = await (0, import_prompts.confirm)({
3391
+ message: "\uC774 \uB0B4\uC6A9\uC73C\uB85C \uB4F1\uB85D\uD560\uAE4C\uC694?",
3392
+ default: true
3393
+ });
3394
+ if (!ok) {
3395
+ process.stderr.write("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n");
3396
+ return;
3397
+ }
3398
+ }
3399
+ await ensureGhInstalled();
3400
+ const bodyFile = (0, import_node_path8.join)((0, import_node_os3.tmpdir)(), `dooray-feedback-${(0, import_node_crypto.randomUUID)()}.md`);
3401
+ await (0, import_promises13.writeFile)(bodyFile, issueBody);
3402
+ try {
3403
+ const args = [
3404
+ "issue",
3405
+ "create",
3406
+ "--repo",
3407
+ TARGET_REPO,
3408
+ "--title",
3409
+ title,
3410
+ "--body-file",
3411
+ bodyFile
3412
+ ];
3413
+ for (const l of labels) {
3414
+ args.push("--label", l);
3415
+ }
3416
+ const { stdout } = await execFileAsync("gh", args);
3417
+ process.stdout.write(stdout);
3418
+ } catch (err) {
3419
+ const msg = err instanceof Error ? err.message : String(err);
3420
+ throw new DoorayCliError(
3421
+ `GitHub issue \uC0DD\uC131 \uC2E4\uD328:
3422
+ ${msg}
3423
+
3424
+ gh \uC778\uC99D \uC548 \uB418\uC5B4 \uC788\uC73C\uBA74: gh auth login`,
3425
+ EXIT_PARAM_ERROR
3426
+ );
3427
+ } finally {
3428
+ await (0, import_promises13.unlink)(bodyFile).catch(() => {
3429
+ });
3430
+ }
3431
+ });
3432
+
2349
3433
  // src/index.ts
2350
- var program = new import_commander33.Command();
2351
- program.name("dooray").description("Dooray REST API CLI").version("0.5.2").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
3434
+ var program = new import_commander40.Command();
3435
+ program.name("dooray").description("Dooray REST API CLI").version("0.5.4").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
2352
3436
  program.hook("preAction", () => {
2353
3437
  const opts = program.opts();
2354
3438
  if (opts.color === false || process.env.NO_COLOR) {
2355
3439
  import_chalk7.default.level = 0;
2356
3440
  }
2357
3441
  });
2358
- var projectCommand = new import_commander33.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
3442
+ var projectCommand = new import_commander40.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
2359
3443
  projectCommand.addCommand(projectListCommand);
2360
3444
  projectCommand.addCommand(projectMembersCommand);
2361
3445
  projectCommand.addCommand(projectWorkflowsCommand);
2362
- var postCommand = new import_commander33.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
3446
+ projectCommand.addCommand(projectGroupsCommand);
3447
+ projectCommand.addCommand(projectTagsCommand);
3448
+ var postCommand = new import_commander40.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
2363
3449
  postCommand.addCommand(postListCommand);
2364
3450
  postCommand.addCommand(postSearchCommand);
2365
3451
  postCommand.addCommand(postGetCommand);
@@ -2367,40 +3453,43 @@ postCommand.addCommand(postEditCommand);
2367
3453
  postCommand.addCommand(postCreateCommand);
2368
3454
  postCommand.addCommand(postDoneCommand);
2369
3455
  postCommand.addCommand(postWorkflowCommand);
2370
- var commentCommand = new import_commander33.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
3456
+ var commentCommand = new import_commander40.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
2371
3457
  commentCommand.addCommand(commentListCommand);
3458
+ commentCommand.addCommand(commentLatestCommand);
2372
3459
  commentCommand.addCommand(commentAddCommand);
2373
3460
  commentCommand.addCommand(commentEditCommand);
2374
3461
  commentCommand.addCommand(commentDeleteCommand);
2375
3462
  postCommand.addCommand(commentCommand);
2376
- var fileCommand = new import_commander33.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
3463
+ var fileCommand = new import_commander40.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
2377
3464
  fileCommand.addCommand(fileListCommand);
2378
3465
  fileCommand.addCommand(fileDownloadCommand);
2379
3466
  fileCommand.addCommand(fileDownloadAllCommand);
2380
3467
  fileCommand.addCommand(fileUploadCommand);
2381
3468
  fileCommand.addCommand(fileDeleteCommand);
2382
3469
  postCommand.addCommand(fileCommand);
2383
- var wikiCommand = new import_commander33.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
3470
+ var wikiCommand = new import_commander40.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
2384
3471
  wikiCommand.addCommand(wikiListCommand);
2385
3472
  wikiCommand.addCommand(wikiPagesCommand);
2386
- var wikiPageCommand = new import_commander33.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
3473
+ var wikiPageCommand = new import_commander40.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
2387
3474
  wikiPageCommand.addCommand(wikiPageGetCommand);
2388
3475
  wikiPageCommand.addCommand(wikiPageCreateCommand);
2389
3476
  wikiPageCommand.addCommand(wikiPageEditCommand);
2390
3477
  wikiCommand.addCommand(wikiPageCommand);
2391
- var mailCommand = new import_commander33.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
3478
+ var mailCommand = new import_commander40.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
2392
3479
  mailCommand.addCommand(mailListCommand);
2393
3480
  mailCommand.addCommand(mailGetCommand);
2394
3481
  mailCommand.addCommand(mailSendCommand);
2395
3482
  mailCommand.addCommand(mailReplyCommand);
2396
3483
  program.addCommand(setupCommand);
2397
3484
  program.addCommand(configCommand);
3485
+ program.addCommand(memberCommand);
2398
3486
  program.addCommand(cacheCommand);
2399
3487
  program.addCommand(doctorCommand);
2400
3488
  program.addCommand(projectCommand);
2401
3489
  program.addCommand(postCommand);
2402
3490
  program.addCommand(wikiCommand);
2403
3491
  program.addCommand(mailCommand);
3492
+ program.addCommand(feedbackCommand);
2404
3493
  program.parseAsync().catch((err) => {
2405
3494
  if (err instanceof DoorayCliError) {
2406
3495
  process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${err.message}`) + "\n");