@bifos/dooray-cli 0.5.2 → 0.5.3

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_commander36 = require("commander");
28
28
  var import_chalk7 = __toESM(require("chalk"));
29
29
 
30
30
  // src/commands/config.ts
@@ -198,6 +198,8 @@ 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");
201
203
  var WIKIS_PATH = (0, import_node_path2.join)(CACHE_DIR, "wikis.json");
202
204
  async function ensureDir2(dir) {
203
205
  await (0, import_promises2.mkdir)(dir, { recursive: true });
@@ -258,6 +260,24 @@ async function getWorkflows(projectId) {
258
260
  async function setWorkflows(projectId, items) {
259
261
  await writeJson(workflowsPath(projectId), { updatedAt: now(), data: items });
260
262
  }
263
+ function tagsPath(projectId) {
264
+ return (0, import_node_path2.join)(TAGS_DIR, `${projectId}.json`);
265
+ }
266
+ async function getTags(projectId) {
267
+ return readJson(tagsPath(projectId));
268
+ }
269
+ async function setTags(projectId, items) {
270
+ await writeJson(tagsPath(projectId), { updatedAt: now(), data: items });
271
+ }
272
+ function milestonesPath(projectId) {
273
+ return (0, import_node_path2.join)(MILESTONES_DIR, `${projectId}.json`);
274
+ }
275
+ async function getMilestones(projectId) {
276
+ return readJson(milestonesPath(projectId));
277
+ }
278
+ async function setMilestones(projectId, items) {
279
+ await writeJson(milestonesPath(projectId), { updatedAt: now(), data: items });
280
+ }
261
281
  async function getWikis() {
262
282
  return readJson(WIKIS_PATH);
263
283
  }
@@ -285,8 +305,20 @@ async function getCacheStats() {
285
305
  workflowProjectCount = files.filter((f) => f.endsWith(".json")).length;
286
306
  } catch {
287
307
  }
308
+ let tagProjectCount = 0;
309
+ try {
310
+ const files = await (0, import_promises2.readdir)(TAGS_DIR);
311
+ tagProjectCount = files.filter((f) => f.endsWith(".json")).length;
312
+ } catch {
313
+ }
314
+ let milestoneProjectCount = 0;
315
+ try {
316
+ const files = await (0, import_promises2.readdir)(MILESTONES_DIR);
317
+ milestoneProjectCount = files.filter((f) => f.endsWith(".json")).length;
318
+ } catch {
319
+ }
288
320
  const me = await getMe();
289
- return { projectCount, memberProjectCount, workflowProjectCount, me: me?.data ?? null };
321
+ return { projectCount, memberProjectCount, workflowProjectCount, tagProjectCount, milestoneProjectCount, me: me?.data ?? null };
290
322
  }
291
323
 
292
324
  // src/commands/cache.ts
@@ -417,6 +449,13 @@ var DoorayApiClient = class {
417
449
  return toDoorayCliError(e);
418
450
  }
419
451
  }
452
+ async getPostStandalone(postId) {
453
+ try {
454
+ return await this.api.get(`project/v1/posts/${postId}`).json();
455
+ } catch (e) {
456
+ return toDoorayCliError(e);
457
+ }
458
+ }
420
459
  async createPost(projectId, body) {
421
460
  try {
422
461
  return await this.api.post(`project/v1/projects/${projectId}/posts`, { json: body }).json();
@@ -520,6 +559,32 @@ var DoorayApiClient = class {
520
559
  return toDoorayCliError(e);
521
560
  }
522
561
  }
562
+ // ─── Tags ───────────────────────────────────────────
563
+ async getProjectTags(projectId, params) {
564
+ try {
565
+ return await this.api.get(`project/v1/projects/${projectId}/tags`, {
566
+ searchParams: {
567
+ ...params?.page != null && { page: params.page },
568
+ ...params?.size != null && { size: params.size }
569
+ }
570
+ }).json();
571
+ } catch (e) {
572
+ return toDoorayCliError(e);
573
+ }
574
+ }
575
+ // ─── Milestones ─────────────────────────────────────
576
+ async getProjectMilestones(projectId, params) {
577
+ try {
578
+ return await this.api.get(`project/v1/projects/${projectId}/milestones`, {
579
+ searchParams: {
580
+ ...params?.page != null && { page: params.page },
581
+ ...params?.size != null && { size: params.size }
582
+ }
583
+ }).json();
584
+ } catch (e) {
585
+ return toDoorayCliError(e);
586
+ }
587
+ }
523
588
  // ─── Wiki ───────────────────────────────────────────
524
589
  async getWikis(params) {
525
590
  try {
@@ -677,6 +742,9 @@ var PROJECTS_TTL_MS = 36e5;
677
742
  var MEMBERS_TTL_MS = 36e5;
678
743
  var WORKFLOWS_TTL_MS = 864e5;
679
744
  var ME_TTL_MS = 864e5;
745
+ var TAGS_TTL_MS = 864e5;
746
+ var MILESTONES_TTL_MS = 864e5;
747
+ var RESOLVER_FETCH_PAGE_SIZE = 100;
680
748
  var WIKIS_TTL_MS = 864e5;
681
749
 
682
750
  // src/resolvers/me.ts
@@ -717,6 +785,8 @@ var doctorCommand = new import_commander3.Command("doctor").description("\uC124\
717
785
  console.log(` \uD504\uB85C\uC81D\uD2B8: ${stats.projectCount}\uAC1C`);
718
786
  console.log(` \uBA64\uBC84: ${stats.memberProjectCount}\uAC1C \uD504\uB85C\uC81D\uD2B8`);
719
787
  console.log(` \uC6CC\uD06C\uD50C\uB85C\uC6B0: ${stats.workflowProjectCount}\uAC1C \uD504\uB85C\uC81D\uD2B8`);
788
+ console.log(` Tag \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.tagProjectCount}`);
789
+ console.log(` Milestone \uCE90\uC2DC (\uD504\uB85C\uC81D\uD2B8 \uC218): ${stats.milestoneProjectCount}`);
720
790
  const claudeDir = import_path.default.join(
721
791
  process.env.HOME ?? process.env.USERPROFILE ?? "",
722
792
  ".claude"
@@ -1024,6 +1094,32 @@ var projectListCommand = new import_commander5.Command("list").description("\uD5
1024
1094
  // src/commands/project/members.ts
1025
1095
  var import_commander6 = require("commander");
1026
1096
 
1097
+ // src/resolvers/match.ts
1098
+ function matchByName(items, input, label, renderCandidate) {
1099
+ const exact = items.filter((i) => i.name === input);
1100
+ if (exact.length === 1) return exact[0];
1101
+ if (exact.length > 1) {
1102
+ throw new DoorayCliError(
1103
+ `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4(\uC815\uD655\uC77C\uCE58): "${input}"
1104
+ ` + exact.map((i) => ` - ${renderCandidate(i)}`).join("\n"),
1105
+ EXIT_PARAM_ERROR
1106
+ );
1107
+ }
1108
+ const partial = items.filter((i) => i.name.includes(input));
1109
+ if (partial.length === 1) return partial[0];
1110
+ if (partial.length > 1) {
1111
+ throw new DoorayCliError(
1112
+ `\uBCF5\uC218\uC758 ${label}\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4: "${input}"
1113
+ ` + partial.map((i) => ` - ${renderCandidate(i)}`).join("\n"),
1114
+ EXIT_PARAM_ERROR
1115
+ );
1116
+ }
1117
+ throw new DoorayCliError(
1118
+ `${label}\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1119
+ EXIT_PARAM_ERROR
1120
+ );
1121
+ }
1122
+
1027
1123
  // src/resolvers/member.ts
1028
1124
  async function fetchAllMembers(client, projectId) {
1029
1125
  const memberIds = [];
@@ -1065,22 +1161,23 @@ async function ensureMembers(client, projectId) {
1065
1161
  await setMembers(projectId, items);
1066
1162
  return items;
1067
1163
  }
1068
- async function resolveMember(client, projectId, input) {
1164
+ async function buildMemberNameMap(client, projectId) {
1069
1165
  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
- );
1166
+ const map = /* @__PURE__ */ new Map();
1167
+ for (const m of members) {
1168
+ if (m.name) map.set(m.organizationMemberId, m.name);
1079
1169
  }
1080
- throw new DoorayCliError(
1081
- `\uBA64\uBC84\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1082
- EXIT_PARAM_ERROR
1170
+ return map;
1171
+ }
1172
+ async function resolveMember(client, projectId, input) {
1173
+ const members = await ensureMembers(client, projectId);
1174
+ const match = matchByName(
1175
+ members,
1176
+ input,
1177
+ "\uBA64\uBC84",
1178
+ (m) => `${m.name} (${m.organizationMemberId})`
1083
1179
  );
1180
+ return match.organizationMemberId;
1084
1181
  }
1085
1182
 
1086
1183
  // src/commands/project/members.ts
@@ -1124,12 +1221,16 @@ async function ensureWorkflows(client, projectId) {
1124
1221
  }
1125
1222
  async function resolveWorkflow(client, projectId, input) {
1126
1223
  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
- );
1224
+ const byClass = workflows.filter((w) => w.class === input);
1225
+ if (byClass.length === 1) return byClass[0].id;
1226
+ if (byClass.length > 1) {
1227
+ throw new DoorayCliError(
1228
+ `\uB3D9\uC77C class\uC758 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4: "${input}"`,
1229
+ EXIT_PARAM_ERROR
1230
+ );
1231
+ }
1232
+ const match = matchByName(workflows, input, "\uC6CC\uD06C\uD50C\uB85C\uC6B0", (w) => `${w.name} [${w.class}] (${w.id})`);
1233
+ return match.id;
1133
1234
  }
1134
1235
 
1135
1236
  // src/commands/project/workflows.ts
@@ -1269,14 +1370,95 @@ async function resolvePost(client, projectId, postNumber) {
1269
1370
  return res.result[0].id;
1270
1371
  }
1271
1372
 
1373
+ // src/utils/dooray-url.ts
1374
+ var TASK_URL_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/to\/(\d+)(?:[/?#].*)?$/;
1375
+ function parseDoorayTaskUrl(input) {
1376
+ const m = TASK_URL_RE.exec(input);
1377
+ return m ? m[1] : null;
1378
+ }
1379
+ function isLikelyDoorayUrl(input) {
1380
+ return /^https?:\/\//.test(input);
1381
+ }
1382
+
1383
+ // src/resolvers/post-input.ts
1384
+ 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";
1385
+ async function resolveByPostId(client, postId) {
1386
+ const res = await client.getPostStandalone(postId);
1387
+ const d = res.result;
1388
+ return {
1389
+ projectId: d.project.id,
1390
+ projectCode: d.project.code,
1391
+ postId: d.id,
1392
+ postNumber: d.number
1393
+ };
1394
+ }
1395
+ async function resolvePostInput(client, args) {
1396
+ const { projectArg, postNumberArg, idOpt, urlOpt } = args;
1397
+ const hasPositional = !!projectArg || !!postNumberArg;
1398
+ if (idOpt && urlOpt) {
1399
+ throw new DoorayCliError(
1400
+ "--id\uC640 --url\uC740 \uB3D9\uC2DC\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1401
+ EXIT_PARAM_ERROR
1402
+ );
1403
+ }
1404
+ if ((idOpt || urlOpt) && hasPositional) {
1405
+ throw new DoorayCliError(
1406
+ "--id/--url\uACFC positional \uC778\uC790(<project> <post-number>)\uB294 \uB3D9\uC2DC\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1407
+ EXIT_PARAM_ERROR
1408
+ );
1409
+ }
1410
+ if (urlOpt) {
1411
+ const postId = parseDoorayTaskUrl(urlOpt);
1412
+ if (!postId) {
1413
+ throw new DoorayCliError(
1414
+ `--url \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${urlOpt}"
1415
+ \uC608: https://x.dooray.com/task/to/4319587406666362045`,
1416
+ EXIT_PARAM_ERROR
1417
+ );
1418
+ }
1419
+ return resolveByPostId(client, postId);
1420
+ }
1421
+ if (idOpt) {
1422
+ return resolveByPostId(client, idOpt);
1423
+ }
1424
+ if (projectArg && !postNumberArg && isLikelyDoorayUrl(projectArg)) {
1425
+ const postId = parseDoorayTaskUrl(projectArg);
1426
+ if (!postId) {
1427
+ throw new DoorayCliError(
1428
+ `Dooray URL \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${projectArg}"
1429
+ \uC608: https://x.dooray.com/task/to/4319587406666362045`,
1430
+ EXIT_PARAM_ERROR
1431
+ );
1432
+ }
1433
+ return resolveByPostId(client, postId);
1434
+ }
1435
+ if (projectArg && postNumberArg) {
1436
+ const projectId = await resolveProject(client, projectArg);
1437
+ const num = Number(postNumberArg);
1438
+ if (!Number.isFinite(num) || num <= 0) {
1439
+ throw new DoorayCliError(
1440
+ `<post-number>\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${postNumberArg}"`,
1441
+ EXIT_PARAM_ERROR
1442
+ );
1443
+ }
1444
+ const postId = await resolvePost(client, projectId, num);
1445
+ return { projectId, projectCode: projectArg, postId, postNumber: num };
1446
+ }
1447
+ throw new DoorayCliError(INPUT_HELP, EXIT_PARAM_ERROR);
1448
+ }
1449
+
1272
1450
  // 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) => {
1451
+ var postGetCommand = new import_commander10.Command("get").description("\uC5C5\uBB34 \uC0C1\uC138 \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
1274
1452
  const globalOpts = postGetCommand.optsWithGlobals();
1275
1453
  const config = await getConfigOrThrow();
1276
1454
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1277
1455
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
1278
- const projectId = await resolveProject(client, project);
1279
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1456
+ const { projectId, postId } = await resolvePostInput(client, {
1457
+ projectArg: project,
1458
+ postNumberArg: postNumberStr,
1459
+ idOpt: opts.id,
1460
+ urlOpt: opts.url
1461
+ });
1280
1462
  const res = await client.getPost(projectId, postId);
1281
1463
  stopSpinner(true, "\uC5C5\uBB34 \uC870\uD68C \uC644\uB8CC");
1282
1464
  formatPostDetail(res.result, globalOpts);
@@ -1453,12 +1635,16 @@ async function resolveUsers(client, projectId, emails) {
1453
1635
  }
1454
1636
  return users;
1455
1637
  }
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) => {
1638
+ var postEditCommand = new import_commander11.Command("edit").description("\uC5C5\uBB34 \uC218\uC815 ($EDITOR \uB610\uB294 --title/--body \uC635\uC158)").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--title <title>", "\uC81C\uBAA9 \uBCC0\uACBD (non-interactive)").option("--subject <subject>", "--title\uC758 deprecated alias").option("--body <text>", "\uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").action(async (project, postNumberStr, opts) => {
1457
1639
  const config = await getConfigOrThrow();
1458
1640
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1459
1641
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
1460
- const projectId = await resolveProject(client, project);
1461
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1642
+ const { projectId, postId, postNumber } = await resolvePostInput(client, {
1643
+ projectArg: project,
1644
+ postNumberArg: postNumberStr,
1645
+ idOpt: opts.id,
1646
+ urlOpt: opts.url
1647
+ });
1462
1648
  const res = await client.getPost(projectId, postId);
1463
1649
  const post = res.result;
1464
1650
  const members = await ensureMembers(client, projectId);
@@ -1518,12 +1704,137 @@ var postEditCommand = new import_commander11.Command("edit").description("\uC5C5
1518
1704
  });
1519
1705
  stopSpinner(true, "\uC5C5\uBB34 \uC218\uC815 \uC644\uB8CC");
1520
1706
  }
1521
- process.stdout.write(`#${postNumberStr} \uC5C5\uBB34\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1707
+ process.stdout.write(`#${postNumber} \uC5C5\uBB34\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1522
1708
  `);
1523
1709
  });
1524
1710
 
1525
1711
  // src/commands/post/create.ts
1526
1712
  var import_commander12 = require("commander");
1713
+
1714
+ // src/resolvers/tag.ts
1715
+ async function fetchAllTags(client, projectId) {
1716
+ const all = [];
1717
+ let page = 0;
1718
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1719
+ while (true) {
1720
+ const res = await client.getProjectTags(projectId, { page, size });
1721
+ for (const t of res.result) {
1722
+ all.push({
1723
+ id: t.id,
1724
+ name: t.name ?? "",
1725
+ groupId: t.tagGroup?.id ?? null,
1726
+ groupName: t.tagGroup?.name ?? null,
1727
+ groupMandatory: t.tagGroup?.mandatory ?? false,
1728
+ groupSelectOne: t.tagGroup?.selectOne ?? false
1729
+ });
1730
+ }
1731
+ const total = res.totalCount ?? all.length;
1732
+ if (all.length >= total) break;
1733
+ page++;
1734
+ }
1735
+ return all;
1736
+ }
1737
+ async function ensureTags(client, projectId) {
1738
+ const entry = await getTags(projectId);
1739
+ if (entry && !isExpired(entry.updatedAt, TAGS_TTL_MS)) return entry.data;
1740
+ const items = await fetchAllTags(client, projectId);
1741
+ await setTags(projectId, items);
1742
+ return items;
1743
+ }
1744
+ async function resolveTags(client, projectId, inputs) {
1745
+ const allTags = await ensureTags(client, projectId);
1746
+ const selected = inputs.map(
1747
+ (input) => matchByName(
1748
+ allTags,
1749
+ input,
1750
+ "\uD0DC\uADF8",
1751
+ (t) => t.groupName ? `${t.groupName} / ${t.name} (${t.id})` : `${t.name} (${t.id})`
1752
+ )
1753
+ );
1754
+ const mandatoryGroups = /* @__PURE__ */ new Map();
1755
+ for (const t of allTags) {
1756
+ if (t.groupMandatory && t.groupId) mandatoryGroups.set(t.groupId, t.groupName ?? t.groupId);
1757
+ }
1758
+ const coveredGroups = new Set(selected.map((t) => t.groupId).filter((g) => !!g));
1759
+ const missing = [];
1760
+ for (const [gid, gname] of mandatoryGroups) {
1761
+ if (!coveredGroups.has(gid)) missing.push(gname);
1762
+ }
1763
+ if (missing.length > 0) {
1764
+ throw new DoorayCliError(
1765
+ `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1766
+ ` + missing.map((g) => ` - ${g}`).join("\n"),
1767
+ EXIT_PARAM_ERROR
1768
+ );
1769
+ }
1770
+ const selectOneGroups = /* @__PURE__ */ new Map();
1771
+ for (const t of selected) {
1772
+ if (!t.groupSelectOne || !t.groupId) continue;
1773
+ const entry = selectOneGroups.get(t.groupId) ?? { name: t.groupName ?? t.groupId, tags: [] };
1774
+ entry.tags.push(t.name);
1775
+ selectOneGroups.set(t.groupId, entry);
1776
+ }
1777
+ const violators = [];
1778
+ for (const [, info] of selectOneGroups) {
1779
+ if (info.tags.length > 1) {
1780
+ violators.push(`${info.name} (\uC120\uD0DD: ${info.tags.join(", ")})`);
1781
+ }
1782
+ }
1783
+ if (violators.length > 0) {
1784
+ throw new DoorayCliError(
1785
+ `\uB2E4\uC74C \uD0DC\uADF8 \uADF8\uB8F9\uC740 1\uAC1C\uB9CC \uC120\uD0DD \uAC00\uB2A5\uD569\uB2C8\uB2E4:
1786
+ ` + violators.map((v) => ` - ${v}`).join("\n"),
1787
+ EXIT_PARAM_ERROR
1788
+ );
1789
+ }
1790
+ return selected.map((t) => t.id);
1791
+ }
1792
+
1793
+ // src/resolvers/milestone.ts
1794
+ async function fetchAllMilestones(client, projectId) {
1795
+ const all = [];
1796
+ let page = 0;
1797
+ const size = RESOLVER_FETCH_PAGE_SIZE;
1798
+ while (true) {
1799
+ const res = await client.getProjectMilestones(projectId, { page, size });
1800
+ for (const m of res.result) all.push({ id: m.id, name: m.name });
1801
+ const total = res.totalCount ?? all.length;
1802
+ if (all.length >= total) break;
1803
+ page++;
1804
+ }
1805
+ return all;
1806
+ }
1807
+ async function ensureMilestones(client, projectId) {
1808
+ const entry = await getMilestones(projectId);
1809
+ if (entry && !isExpired(entry.updatedAt, MILESTONES_TTL_MS)) return entry.data;
1810
+ const items = await fetchAllMilestones(client, projectId);
1811
+ await setMilestones(projectId, items);
1812
+ return items;
1813
+ }
1814
+ async function resolveMilestone(client, projectId, input) {
1815
+ const all = await ensureMilestones(client, projectId);
1816
+ const match = matchByName(all, input, "\uB9C8\uC77C\uC2A4\uD1A4", (m) => `${m.name} (${m.id})`);
1817
+ return match.id;
1818
+ }
1819
+
1820
+ // src/resolvers/postRef.ts
1821
+ async function resolvePostRef(client, ref) {
1822
+ if (ref.includes("/")) {
1823
+ const [code, numStr] = ref.split("/", 2);
1824
+ const num = Number(numStr);
1825
+ if (!code || !Number.isFinite(num) || num <= 0) {
1826
+ throw new DoorayCliError(
1827
+ `--parent \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${ref}" (\uC608: "tc-ocr/337" \uB610\uB294 raw postId)`,
1828
+ EXIT_PARAM_ERROR
1829
+ );
1830
+ }
1831
+ const projectId = await resolveProject(client, code);
1832
+ return resolvePost(client, projectId, num);
1833
+ }
1834
+ return ref;
1835
+ }
1836
+
1837
+ // src/commands/post/create.ts
1527
1838
  async function resolveUsers2(client, projectId, inputs) {
1528
1839
  const users = [];
1529
1840
  for (const input of inputs) {
@@ -1532,7 +1843,7 @@ async function resolveUsers2(client, projectId, inputs) {
1532
1843
  }
1533
1844
  return users;
1534
1845
  }
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) => {
1846
+ var postCreateCommand = new import_commander12.Command("create").description("\uC5C5\uBB34 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--title <title>", "\uC5C5\uBB34 \uC81C\uBAA9").option("--subject <subject>", "--title\uC758 deprecated alias").option("--to <members...>", "\uB2F4\uB2F9\uC790 (\uC774\uB984 \uB610\uB294 \uC774\uBA54\uC77C, \uC5EC\uB7EC \uBA85 \uAC00\uB2A5)").option("--cc <members...>", "\uCC38\uC870\uC790 (\uC774\uB984 \uB610\uB294 \uC774\uBA54\uC77C, \uC5EC\uB7EC \uBA85 \uAC00\uB2A5)").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--priority <level>", "\uC6B0\uC120\uC21C\uC704 (highest, high, normal, low, lowest)", "normal").option("--due-date <date>", "\uB9C8\uAC10\uC77C (ISO 8601 \uD615\uC2DD)").option("--tag <name>", "\uD0DC\uADF8 \uC774\uB984 (\uBC18\uBCF5 \uAC00\uB2A5)", (value, prev) => [...prev, value], []).option("--parent <ref>", "\uBD80\uBAA8 \uC5C5\uBB34 (project/number \uB610\uB294 postId)").option("--workflow <name>", "\uCD08\uAE30 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 class").option("--milestone <name>", "\uB9C8\uC77C\uC2A4\uD1A4 \uC774\uB984").action(async (project, opts) => {
1536
1847
  const globalOpts = postCreateCommand.optsWithGlobals();
1537
1848
  const config = await getConfigOrThrow();
1538
1849
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1553,14 +1864,33 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1553
1864
  const projectId = await resolveProject(client, project);
1554
1865
  const toUsers = opts.to ? await resolveUsers2(client, projectId, opts.to) : [];
1555
1866
  const ccUsers = opts.cc ? await resolveUsers2(client, projectId, opts.cc) : [];
1867
+ const tagInputs = (opts.tag ?? []).filter((s) => s.length > 0);
1868
+ const [tagIds, parentPostId, milestoneId] = await Promise.all([
1869
+ tagInputs.length > 0 ? resolveTags(client, projectId, tagInputs) : Promise.resolve(void 0),
1870
+ opts.parent ? resolvePostRef(client, opts.parent) : Promise.resolve(void 0),
1871
+ opts.milestone ? resolveMilestone(client, projectId, opts.milestone) : Promise.resolve(void 0)
1872
+ ]);
1556
1873
  const res = await client.createPost(projectId, {
1557
1874
  subject,
1558
1875
  body: { mimeType: "text/x-markdown", content: bodyContent },
1559
1876
  users: { to: toUsers, cc: ccUsers },
1560
1877
  priority: opts.priority,
1561
- ...opts.dueDate && { dueDate: opts.dueDate, dueDateFlag: true }
1878
+ ...opts.dueDate && { dueDate: opts.dueDate, dueDateFlag: true },
1879
+ ...parentPostId && { parentPostId },
1880
+ ...milestoneId && { milestoneId },
1881
+ ...tagIds && tagIds.length > 0 && { tagIds }
1562
1882
  });
1563
1883
  stopSpinner(true, "\uC5C5\uBB34 \uC0DD\uC131 \uC644\uB8CC");
1884
+ if (opts.workflow) {
1885
+ try {
1886
+ const workflowId = await resolveWorkflow(client, projectId, opts.workflow);
1887
+ await client.setPostWorkflow(projectId, res.result.id, workflowId);
1888
+ } catch (err) {
1889
+ const msg = err instanceof Error ? err.message : String(err);
1890
+ process.stderr.write(`\u26A0 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815 \uC2E4\uD328 (post\uB294 \uC0DD\uC131\uB428): ${msg}
1891
+ `);
1892
+ }
1893
+ }
1564
1894
  if (globalOpts.json) {
1565
1895
  printJson(res.result);
1566
1896
  } else if (globalOpts.quiet) {
@@ -1573,53 +1903,116 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1573
1903
 
1574
1904
  // src/commands/post/done.ts
1575
1905
  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) => {
1906
+ var postDoneCommand = new import_commander13.Command("done").description("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
1577
1907
  const config = await getConfigOrThrow();
1578
1908
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1579
1909
  startSpinner("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC \uC911...");
1580
- const projectId = await resolveProject(client, project);
1581
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1910
+ const { projectId, postId, postNumber } = await resolvePostInput(client, {
1911
+ projectArg: project,
1912
+ postNumberArg: postNumberStr,
1913
+ idOpt: opts.id,
1914
+ urlOpt: opts.url
1915
+ });
1582
1916
  await client.setPostDone(projectId, postId);
1583
1917
  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.
1918
+ process.stdout.write(`#${postNumber} \uC5C5\uBB34\uAC00 \uC644\uB8CC \uCC98\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1585
1919
  `);
1586
1920
  });
1587
1921
 
1588
1922
  // src/commands/post/workflow.ts
1589
1923
  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) => {
1924
+ var postWorkflowCommand = new import_commander14.Command("workflow").description("\uC5C5\uBB34 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").argument("[workflow]", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 \uB610\uB294 \uD074\uB798\uC2A4 (\uB610\uB294 --workflow \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--workflow <name>", "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC774\uB984 (positional \uB300\uCCB4)").action(async (project, postNumber, workflowArg, opts) => {
1591
1925
  const config = await getConfigOrThrow();
1592
1926
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1927
+ let workflowInput = opts.workflow;
1928
+ let projectArg = project;
1929
+ let postNumberArg = postNumber;
1930
+ if (!workflowInput) {
1931
+ if (workflowArg) {
1932
+ workflowInput = workflowArg;
1933
+ } else if ((opts.id || opts.url) && projectArg && !postNumberArg) {
1934
+ workflowInput = projectArg;
1935
+ projectArg = void 0;
1936
+ } else if (!opts.id && !opts.url && projectArg && postNumberArg && !workflowArg) {
1937
+ if (isLikelyDoorayUrl(projectArg)) {
1938
+ workflowInput = postNumberArg;
1939
+ postNumberArg = void 0;
1940
+ }
1941
+ }
1942
+ }
1943
+ if (!workflowInput) {
1944
+ throw new DoorayCliError(
1945
+ "workflow\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional \uB610\uB294 --workflow\uB85C \uC785\uB825\uD558\uC138\uC694.",
1946
+ EXIT_PARAM_ERROR
1947
+ );
1948
+ }
1593
1949
  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);
1950
+ const { projectId, postId, postNumber: resolvedPostNumber } = await resolvePostInput(client, {
1951
+ projectArg,
1952
+ postNumberArg,
1953
+ idOpt: opts.id,
1954
+ urlOpt: opts.url
1955
+ });
1956
+ const workflowId = await resolveWorkflow(client, projectId, workflowInput);
1597
1957
  await client.setPostWorkflow(projectId, postId, workflowId);
1598
1958
  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.
1959
+ process.stdout.write(`#${resolvedPostNumber} \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 "${workflowInput}"(\uC73C)\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
1600
1960
  `);
1601
1961
  });
1602
1962
 
1603
1963
  // src/commands/post/comment/list.ts
1604
1964
  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) => {
1965
+
1966
+ // src/utils/comment-enrich.ts
1967
+ function enrichCommentCreators(comments, nameMap) {
1968
+ return comments.map((c) => {
1969
+ const id = c.creator?.member?.organizationMemberId;
1970
+ const existing = c.creator?.member?.name;
1971
+ if (existing || !id) return c;
1972
+ const filled = nameMap.get(id);
1973
+ if (!filled) return c;
1974
+ return {
1975
+ ...c,
1976
+ creator: {
1977
+ ...c.creator,
1978
+ member: { ...c.creator.member, name: filled }
1979
+ }
1980
+ };
1981
+ });
1982
+ }
1983
+
1984
+ // src/commands/post/comment/list.ts
1985
+ var commentListCommand = new import_commander15.Command("list").description("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (project, postNumberStr, opts) => {
1606
1986
  const globalOpts = commentListCommand.optsWithGlobals();
1607
1987
  const config = await getConfigOrThrow();
1608
1988
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1609
1989
  startSpinner("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC911...");
1610
- const projectId = await resolveProject(client, project);
1611
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1990
+ const { projectId, postId } = await resolvePostInput(client, {
1991
+ projectArg: project,
1992
+ postNumberArg: postNumberStr,
1993
+ idOpt: opts.id,
1994
+ urlOpt: opts.url
1995
+ });
1612
1996
  const res = await client.getPostComments(projectId, postId, {
1613
1997
  page: Number(opts.page),
1614
1998
  size: Number(opts.size)
1615
1999
  });
1616
2000
  stopSpinner(true, "\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1617
- formatCommentList(res.result, globalOpts);
2001
+ let comments = res.result;
2002
+ if (!globalOpts.json) {
2003
+ let nameMap = /* @__PURE__ */ new Map();
2004
+ try {
2005
+ nameMap = await buildMemberNameMap(client, projectId);
2006
+ } catch {
2007
+ }
2008
+ comments = enrichCommentCreators(comments, nameMap);
2009
+ }
2010
+ formatCommentList(comments, globalOpts);
1618
2011
  });
1619
2012
 
1620
2013
  // src/commands/post/comment/add.ts
1621
2014
  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) => {
2015
+ var commentAddCommand = new import_commander16.Command("add").description("\uB313\uAE00 \uCD94\uAC00").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, postNumberStr, opts) => {
1623
2016
  const globalOpts = commentAddCommand.optsWithGlobals();
1624
2017
  const config = await getConfigOrThrow();
1625
2018
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -1632,8 +2025,12 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
1632
2025
  }
1633
2026
  }
1634
2027
  startSpinner("\uB313\uAE00 \uCD94\uAC00 \uC911...");
1635
- const projectId = await resolveProject(client, project);
1636
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2028
+ const { projectId, postId } = await resolvePostInput(client, {
2029
+ projectArg: project,
2030
+ postNumberArg: postNumberStr,
2031
+ idOpt: opts.id,
2032
+ urlOpt: opts.url
2033
+ });
1637
2034
  const res = await client.createPostComment(projectId, postId, {
1638
2035
  body: { mimeType: "text/x-markdown", content: bodyContent }
1639
2036
  });
@@ -1650,12 +2047,55 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
1650
2047
 
1651
2048
  // src/commands/post/comment/edit.ts
1652
2049
  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) => {
2050
+ var commentEditCommand = new import_commander17.Command("edit").description("\uB313\uAE00 \uC218\uC815 ($EDITOR \uB610\uB294 --body \uC635\uC158)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").action(async (arg1, arg2, arg3, opts) => {
1654
2051
  const config = await getConfigOrThrow();
1655
2052
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2053
+ let projectArg;
2054
+ let postNumberArg;
2055
+ let commentId = opts.commentId;
2056
+ if (opts.id || opts.url) {
2057
+ if (arg2 || arg3) {
2058
+ throw new DoorayCliError(
2059
+ "--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.",
2060
+ EXIT_PARAM_ERROR
2061
+ );
2062
+ }
2063
+ commentId = commentId ?? arg1;
2064
+ } else if (arg3) {
2065
+ projectArg = arg1;
2066
+ postNumberArg = arg2;
2067
+ commentId = commentId ?? arg3;
2068
+ } else if (arg1 && !arg2) {
2069
+ projectArg = arg1;
2070
+ if (!commentId) {
2071
+ throw new DoorayCliError(
2072
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --comment-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2073
+ EXIT_PARAM_ERROR
2074
+ );
2075
+ }
2076
+ } else {
2077
+ projectArg = arg1;
2078
+ postNumberArg = arg2;
2079
+ if (!commentId) {
2080
+ throw new DoorayCliError(
2081
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2082
+ EXIT_PARAM_ERROR
2083
+ );
2084
+ }
2085
+ }
2086
+ if (!commentId) {
2087
+ throw new DoorayCliError(
2088
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2089
+ EXIT_PARAM_ERROR
2090
+ );
2091
+ }
1656
2092
  startSpinner("\uB313\uAE00 \uC870\uD68C \uC911...");
1657
- const projectId = await resolveProject(client, project);
1658
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2093
+ const { projectId, postId } = await resolvePostInput(client, {
2094
+ projectArg,
2095
+ postNumberArg,
2096
+ idOpt: opts.id,
2097
+ urlOpt: opts.url
2098
+ });
1659
2099
  const comments = await client.getPostComments(projectId, postId);
1660
2100
  const comment = comments.result.find((c) => c.id === commentId);
1661
2101
  stopSpinner(true, "\uB313\uAE00 \uC870\uD68C \uC644\uB8CC");
@@ -1684,12 +2124,55 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
1684
2124
 
1685
2125
  // src/commands/post/comment/delete.ts
1686
2126
  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) => {
2127
+ var commentDeleteCommand = new import_commander18.Command("delete").description("\uB313\uAE00 \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
1688
2128
  const config = await getConfigOrThrow();
1689
2129
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2130
+ let projectArg;
2131
+ let postNumberArg;
2132
+ let commentId = opts.commentId;
2133
+ if (opts.id || opts.url) {
2134
+ if (arg2 || arg3) {
2135
+ throw new DoorayCliError(
2136
+ "--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.",
2137
+ EXIT_PARAM_ERROR
2138
+ );
2139
+ }
2140
+ commentId = commentId ?? arg1;
2141
+ } else if (arg3) {
2142
+ projectArg = arg1;
2143
+ postNumberArg = arg2;
2144
+ commentId = commentId ?? arg3;
2145
+ } else if (arg1 && !arg2) {
2146
+ projectArg = arg1;
2147
+ if (!commentId) {
2148
+ throw new DoorayCliError(
2149
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --comment-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2150
+ EXIT_PARAM_ERROR
2151
+ );
2152
+ }
2153
+ } else {
2154
+ projectArg = arg1;
2155
+ postNumberArg = arg2;
2156
+ if (!commentId) {
2157
+ throw new DoorayCliError(
2158
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2159
+ EXIT_PARAM_ERROR
2160
+ );
2161
+ }
2162
+ }
2163
+ if (!commentId) {
2164
+ throw new DoorayCliError(
2165
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2166
+ EXIT_PARAM_ERROR
2167
+ );
2168
+ }
1690
2169
  startSpinner("\uB313\uAE00 \uC0AD\uC81C \uC911...");
1691
- const projectId = await resolveProject(client, project);
1692
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2170
+ const { projectId, postId } = await resolvePostInput(client, {
2171
+ projectArg,
2172
+ postNumberArg,
2173
+ idOpt: opts.id,
2174
+ urlOpt: opts.url
2175
+ });
1693
2176
  await client.deletePostComment(projectId, postId, commentId);
1694
2177
  stopSpinner(true, "\uB313\uAE00 \uC0AD\uC81C \uC644\uB8CC");
1695
2178
  process.stdout.write(`\uB313\uAE00\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${commentId}
@@ -1703,13 +2186,17 @@ function formatSize(bytes) {
1703
2186
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
1704
2187
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1705
2188
  }
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) => {
2189
+ var fileListCommand = new import_commander19.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
1707
2190
  const globalOpts = fileListCommand.optsWithGlobals();
1708
2191
  const config = await getConfigOrThrow();
1709
2192
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1710
2193
  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));
2194
+ const { projectId, postId } = await resolvePostInput(client, {
2195
+ projectArg: project,
2196
+ postNumberArg: postNumberStr,
2197
+ idOpt: opts.id,
2198
+ urlOpt: opts.url
2199
+ });
1713
2200
  const res = await client.getPostFiles(projectId, postId);
1714
2201
  stopSpinner(true, `\uCCA8\uBD80\uD30C\uC77C ${res.result.length}\uAC1C`);
1715
2202
  output(globalOpts, {
@@ -1730,12 +2217,52 @@ var fileListCommand = new import_commander19.Command("list").description("\uC5C5
1730
2217
  var import_commander20 = require("commander");
1731
2218
  var import_promises8 = require("fs/promises");
1732
2219
  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) => {
2220
+ var fileDownloadCommand = new import_commander20.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (arg1, arg2, arg3, opts) => {
1734
2221
  const config = await getConfigOrThrow();
1735
2222
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2223
+ let projectArg;
2224
+ let postNumberArg;
2225
+ let fileId = opts.fileId;
2226
+ if (opts.id || opts.url) {
2227
+ if (arg2 || arg3) {
2228
+ throw new DoorayCliError(
2229
+ "--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.",
2230
+ EXIT_PARAM_ERROR
2231
+ );
2232
+ }
2233
+ fileId = fileId ?? arg1;
2234
+ } else if (arg3) {
2235
+ projectArg = arg1;
2236
+ postNumberArg = arg2;
2237
+ fileId = fileId ?? arg3;
2238
+ } else if (arg1 && !arg2) {
2239
+ projectArg = arg1;
2240
+ if (!fileId) {
2241
+ throw new DoorayCliError(
2242
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --file-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2243
+ EXIT_PARAM_ERROR
2244
+ );
2245
+ }
2246
+ } else {
2247
+ projectArg = arg1;
2248
+ postNumberArg = arg2;
2249
+ if (!fileId) {
2250
+ throw new DoorayCliError(
2251
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2252
+ EXIT_PARAM_ERROR
2253
+ );
2254
+ }
2255
+ }
2256
+ if (!fileId) {
2257
+ throw new DoorayCliError("\uD30C\uC77C ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2258
+ }
1736
2259
  startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1737
- const projectId = await resolveProject(client, project);
1738
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2260
+ const { projectId, postId } = await resolvePostInput(client, {
2261
+ projectArg,
2262
+ postNumberArg,
2263
+ idOpt: opts.id,
2264
+ urlOpt: opts.url
2265
+ });
1739
2266
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
1740
2267
  const outputPath = (0, import_node_path4.join)(opts.output, fileName);
1741
2268
  await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
@@ -1748,12 +2275,16 @@ var fileDownloadCommand = new import_commander20.Command("download").description
1748
2275
  var import_commander21 = require("commander");
1749
2276
  var import_promises9 = require("fs/promises");
1750
2277
  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) => {
2278
+ var fileDownloadAllCommand = new import_commander21.Command("download-all").description("\uC5C5\uBB34\uC758 \uBAA8\uB4E0 \uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
1752
2279
  const config = await getConfigOrThrow();
1753
2280
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1754
2281
  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));
2282
+ const { projectId, postId } = await resolvePostInput(client, {
2283
+ projectArg: project,
2284
+ postNumberArg: postNumberStr,
2285
+ idOpt: opts.id,
2286
+ urlOpt: opts.url
2287
+ });
1757
2288
  const res = await client.getPostFiles(projectId, postId);
1758
2289
  if (res.result.length === 0) {
1759
2290
  stopSpinner(true, "\uCCA8\uBD80\uD30C\uC77C \uC5C6\uC74C");
@@ -1779,13 +2310,53 @@ var fileDownloadAllCommand = new import_commander21.Command("download-all").desc
1779
2310
  // src/commands/post/file/upload.ts
1780
2311
  var import_commander22 = require("commander");
1781
2312
  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) => {
2313
+ var fileUploadCommand = new import_commander22.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
1783
2314
  const globalOpts = fileUploadCommand.optsWithGlobals();
1784
2315
  const config = await getConfigOrThrow();
1785
2316
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2317
+ let projectArg;
2318
+ let postNumberArg;
2319
+ let filePath = opts.file;
2320
+ if (opts.id || opts.url) {
2321
+ if (arg2 || arg3) {
2322
+ throw new DoorayCliError(
2323
+ "--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.",
2324
+ EXIT_PARAM_ERROR
2325
+ );
2326
+ }
2327
+ filePath = filePath ?? arg1;
2328
+ } else if (arg3) {
2329
+ projectArg = arg1;
2330
+ postNumberArg = arg2;
2331
+ filePath = filePath ?? arg3;
2332
+ } else if (arg1 && !arg2) {
2333
+ projectArg = arg1;
2334
+ if (!filePath) {
2335
+ throw new DoorayCliError(
2336
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --file \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2337
+ EXIT_PARAM_ERROR
2338
+ );
2339
+ }
2340
+ } else {
2341
+ projectArg = arg1;
2342
+ postNumberArg = arg2;
2343
+ if (!filePath) {
2344
+ throw new DoorayCliError(
2345
+ "<file-path>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --file \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2346
+ EXIT_PARAM_ERROR
2347
+ );
2348
+ }
2349
+ }
2350
+ if (!filePath) {
2351
+ throw new DoorayCliError("\uD30C\uC77C \uACBD\uB85C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2352
+ }
1786
2353
  startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
1787
- const projectId = await resolveProject(client, project);
1788
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2354
+ const { projectId, postId } = await resolvePostInput(client, {
2355
+ projectArg,
2356
+ postNumberArg,
2357
+ idOpt: opts.id,
2358
+ urlOpt: opts.url
2359
+ });
1789
2360
  const res = await client.uploadPostFile(projectId, postId, filePath);
1790
2361
  stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
1791
2362
  if (globalOpts.json) {
@@ -1801,12 +2372,52 @@ var fileUploadCommand = new import_commander22.Command("upload").description("\u
1801
2372
 
1802
2373
  // src/commands/post/file/delete.ts
1803
2374
  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) => {
2375
+ var fileDeleteCommand = new import_commander23.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
1805
2376
  const config = await getConfigOrThrow();
1806
2377
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2378
+ let projectArg;
2379
+ let postNumberArg;
2380
+ let fileId = opts.fileId;
2381
+ if (opts.id || opts.url) {
2382
+ if (arg2 || arg3) {
2383
+ throw new DoorayCliError(
2384
+ "--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.",
2385
+ EXIT_PARAM_ERROR
2386
+ );
2387
+ }
2388
+ fileId = fileId ?? arg1;
2389
+ } else if (arg3) {
2390
+ projectArg = arg1;
2391
+ postNumberArg = arg2;
2392
+ fileId = fileId ?? arg3;
2393
+ } else if (arg1 && !arg2) {
2394
+ projectArg = arg1;
2395
+ if (!fileId) {
2396
+ throw new DoorayCliError(
2397
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --file-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2398
+ EXIT_PARAM_ERROR
2399
+ );
2400
+ }
2401
+ } else {
2402
+ projectArg = arg1;
2403
+ postNumberArg = arg2;
2404
+ if (!fileId) {
2405
+ throw new DoorayCliError(
2406
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2407
+ EXIT_PARAM_ERROR
2408
+ );
2409
+ }
2410
+ }
2411
+ if (!fileId) {
2412
+ throw new DoorayCliError("\uD30C\uC77C ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2413
+ }
1807
2414
  startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
1808
- const projectId = await resolveProject(client, project);
1809
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
2415
+ const { projectId, postId } = await resolvePostInput(client, {
2416
+ projectArg,
2417
+ postNumberArg,
2418
+ idOpt: opts.id,
2419
+ urlOpt: opts.url
2420
+ });
1810
2421
  await client.deletePostFile(projectId, postId, fileId);
1811
2422
  stopSpinner(true, "\uC0AD\uC81C \uC644\uB8CC");
1812
2423
  process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
@@ -2019,9 +2630,74 @@ var wikiPageEditCommand = new import_commander28.Command("edit").description("\u
2019
2630
  `);
2020
2631
  });
2021
2632
 
2022
- // src/commands/mail/list.ts
2633
+ // src/commands/member/index.ts
2634
+ var import_commander31 = require("commander");
2635
+
2636
+ // src/commands/member/get.ts
2023
2637
  var import_commander29 = require("commander");
2024
2638
 
2639
+ // src/formatters/member.ts
2640
+ function formatMemberDetail(member, opts) {
2641
+ if (opts.json) {
2642
+ printJson(member);
2643
+ return;
2644
+ }
2645
+ if (opts.quiet) {
2646
+ process.stdout.write(member.id + "\n");
2647
+ return;
2648
+ }
2649
+ const lines = [
2650
+ `\uC774\uB984: ${member.name}`,
2651
+ ...member.englishName ? [`\uC601\uBB38\uBA85: ${member.englishName}`] : [],
2652
+ ...member.nickname ? [`\uBCC4\uBA85: ${member.nickname}`] : [],
2653
+ ...member.userCode ? [`\uC0AC\uBC88/ID: ${member.userCode}`] : [],
2654
+ ...member.externalEmailAddress ? [`\uC678\uBD80 \uC774\uBA54\uC77C: ${member.externalEmailAddress}`] : [],
2655
+ `member-id: ${member.id}`
2656
+ ];
2657
+ process.stdout.write(lines.join("\n") + "\n");
2658
+ }
2659
+ function formatMemberList(rows, opts) {
2660
+ output(opts, {
2661
+ headers: ["ID", "Name", "Role"],
2662
+ rows: rows.map((r) => [r.id, r.name, r.role ?? ""]),
2663
+ raw: rows,
2664
+ ids: rows.map((r) => r.id)
2665
+ });
2666
+ }
2667
+
2668
+ // src/commands/member/get.ts
2669
+ var memberGetCommand = new import_commander29.Command("get").description("\uBA64\uBC84 \uC0C1\uC138 \uC815\uBCF4 \uC870\uD68C (organizationMemberId)").argument("<member-id>", "\uC870\uD68C\uD560 organization member ID").action(async (memberId) => {
2670
+ const globalOpts = memberGetCommand.optsWithGlobals();
2671
+ const config = await getConfigOrThrow();
2672
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2673
+ const res = await client.getMemberDetail(memberId);
2674
+ formatMemberDetail(res.result, globalOpts);
2675
+ });
2676
+
2677
+ // src/commands/member/list.ts
2678
+ var import_commander30 = require("commander");
2679
+ var memberListCommand = new import_commander30.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uBAA9\uB85D").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
2680
+ const globalOpts = memberListCommand.optsWithGlobals();
2681
+ const config = await getConfigOrThrow();
2682
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2683
+ startSpinner("\uBA64\uBC84 \uBAA9\uB85D \uC870\uD68C \uC911...");
2684
+ const projectId = await resolveProject(client, project);
2685
+ const members = await ensureMembers(client, projectId);
2686
+ stopSpinner(true, `${members.length}\uBA85`);
2687
+ formatMemberList(
2688
+ members.map((m) => ({ id: m.organizationMemberId, name: m.name })),
2689
+ globalOpts
2690
+ );
2691
+ });
2692
+
2693
+ // src/commands/member/index.ts
2694
+ var memberCommand = new import_commander31.Command("member").description("Dooray \uBA64\uBC84 \uC870\uD68C");
2695
+ memberCommand.addCommand(memberGetCommand);
2696
+ memberCommand.addCommand(memberListCommand);
2697
+
2698
+ // src/commands/mail/list.ts
2699
+ var import_commander32 = require("commander");
2700
+
2025
2701
  // src/api/imapClient.ts
2026
2702
  var import_imapflow = require("imapflow");
2027
2703
  var import_mailparser = require("mailparser");
@@ -2128,7 +2804,7 @@ async function getMail(config, uid) {
2128
2804
 
2129
2805
  // src/commands/mail/list.ts
2130
2806
  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) => {
2807
+ var mailListCommand = new import_commander32.Command("list").description("\uBA54\uC77C \uBAA9\uB85D \uC870\uD68C").option("--unread", "\uC548\uC77D\uC740 \uBA54\uC77C\uB9CC").option("--search <keyword>", "\uC81C\uBAA9 \uAC80\uC0C9").option("--size <number>", "\uC870\uD68C \uAC1C\uC218", "20").action(async (opts) => {
2132
2808
  const globalOpts = mailListCommand.optsWithGlobals();
2133
2809
  const config = await getConfigOrThrow();
2134
2810
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2160,9 +2836,9 @@ var mailListCommand = new import_commander29.Command("list").description("\uBA54
2160
2836
  });
2161
2837
 
2162
2838
  // src/commands/mail/get.ts
2163
- var import_commander30 = require("commander");
2839
+ var import_commander33 = require("commander");
2164
2840
  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) => {
2841
+ var mailGetCommand = new import_commander33.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
2166
2842
  const globalOpts = mailGetCommand.optsWithGlobals();
2167
2843
  const config = await getConfigOrThrow();
2168
2844
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2193,7 +2869,7 @@ ${mail.body}
2193
2869
  });
2194
2870
 
2195
2871
  // src/commands/mail/send.ts
2196
- var import_commander31 = require("commander");
2872
+ var import_commander34 = require("commander");
2197
2873
  var import_promises10 = require("fs/promises");
2198
2874
 
2199
2875
  // src/api/smtpClient.ts
@@ -2243,7 +2919,7 @@ async function sendMail(config, opts) {
2243
2919
  }
2244
2920
 
2245
2921
  // 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) => {
2922
+ var mailSendCommand = new import_commander34.Command("send").description("\uBA54\uC77C \uBC1C\uC1A1").requiredOption("--to <addresses...>", "\uBC1B\uB294\uC0AC\uB78C \uC774\uBA54\uC77C (\uBCF5\uC218 \uAC00\uB2A5)").requiredOption("--subject <title>", "\uC81C\uBAA9").option("--body <text>", "\uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--bcc <addresses...>", "\uC228\uC740\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (opts) => {
2247
2923
  const globalOpts = mailSendCommand.optsWithGlobals();
2248
2924
  const config = await getConfigOrThrow();
2249
2925
  let body = opts.body ?? "";
@@ -2278,7 +2954,7 @@ var mailSendCommand = new import_commander31.Command("send").description("\uBA54
2278
2954
  });
2279
2955
 
2280
2956
  // src/commands/mail/reply.ts
2281
- var import_commander32 = require("commander");
2957
+ var import_commander35 = require("commander");
2282
2958
  var import_promises11 = require("fs/promises");
2283
2959
  var import_imapflow2 = require("imapflow");
2284
2960
  async function getMessageId(config, uid) {
@@ -2306,7 +2982,7 @@ async function getMessageId(config, uid) {
2306
2982
  await client.logout();
2307
2983
  }
2308
2984
  }
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) => {
2985
+ var mailReplyCommand = new import_commander35.Command("reply").description("\uBA54\uC77C \uB2F5\uC7A5").argument("<uid>", "\uC6D0\uBCF8 \uBA54\uC77C UID").option("--body <text>", "\uB2F5\uC7A5 \uBCF8\uBB38").option("--body-file <path>", "\uB2F5\uC7A5 \uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (uid, opts) => {
2310
2986
  const globalOpts = mailReplyCommand.optsWithGlobals();
2311
2987
  const config = await getConfigOrThrow();
2312
2988
  let body = opts.body ?? "";
@@ -2347,19 +3023,19 @@ var mailReplyCommand = new import_commander32.Command("reply").description("\uBA
2347
3023
  });
2348
3024
 
2349
3025
  // 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");
3026
+ var program = new import_commander36.Command();
3027
+ program.name("dooray").description("Dooray REST API CLI").version("0.5.3").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
2352
3028
  program.hook("preAction", () => {
2353
3029
  const opts = program.opts();
2354
3030
  if (opts.color === false || process.env.NO_COLOR) {
2355
3031
  import_chalk7.default.level = 0;
2356
3032
  }
2357
3033
  });
2358
- var projectCommand = new import_commander33.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
3034
+ var projectCommand = new import_commander36.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
2359
3035
  projectCommand.addCommand(projectListCommand);
2360
3036
  projectCommand.addCommand(projectMembersCommand);
2361
3037
  projectCommand.addCommand(projectWorkflowsCommand);
2362
- var postCommand = new import_commander33.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
3038
+ var postCommand = new import_commander36.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
2363
3039
  postCommand.addCommand(postListCommand);
2364
3040
  postCommand.addCommand(postSearchCommand);
2365
3041
  postCommand.addCommand(postGetCommand);
@@ -2367,34 +3043,35 @@ postCommand.addCommand(postEditCommand);
2367
3043
  postCommand.addCommand(postCreateCommand);
2368
3044
  postCommand.addCommand(postDoneCommand);
2369
3045
  postCommand.addCommand(postWorkflowCommand);
2370
- var commentCommand = new import_commander33.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
3046
+ var commentCommand = new import_commander36.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
2371
3047
  commentCommand.addCommand(commentListCommand);
2372
3048
  commentCommand.addCommand(commentAddCommand);
2373
3049
  commentCommand.addCommand(commentEditCommand);
2374
3050
  commentCommand.addCommand(commentDeleteCommand);
2375
3051
  postCommand.addCommand(commentCommand);
2376
- var fileCommand = new import_commander33.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
3052
+ var fileCommand = new import_commander36.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
2377
3053
  fileCommand.addCommand(fileListCommand);
2378
3054
  fileCommand.addCommand(fileDownloadCommand);
2379
3055
  fileCommand.addCommand(fileDownloadAllCommand);
2380
3056
  fileCommand.addCommand(fileUploadCommand);
2381
3057
  fileCommand.addCommand(fileDeleteCommand);
2382
3058
  postCommand.addCommand(fileCommand);
2383
- var wikiCommand = new import_commander33.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
3059
+ var wikiCommand = new import_commander36.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
2384
3060
  wikiCommand.addCommand(wikiListCommand);
2385
3061
  wikiCommand.addCommand(wikiPagesCommand);
2386
- var wikiPageCommand = new import_commander33.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
3062
+ var wikiPageCommand = new import_commander36.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
2387
3063
  wikiPageCommand.addCommand(wikiPageGetCommand);
2388
3064
  wikiPageCommand.addCommand(wikiPageCreateCommand);
2389
3065
  wikiPageCommand.addCommand(wikiPageEditCommand);
2390
3066
  wikiCommand.addCommand(wikiPageCommand);
2391
- var mailCommand = new import_commander33.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
3067
+ var mailCommand = new import_commander36.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
2392
3068
  mailCommand.addCommand(mailListCommand);
2393
3069
  mailCommand.addCommand(mailGetCommand);
2394
3070
  mailCommand.addCommand(mailSendCommand);
2395
3071
  mailCommand.addCommand(mailReplyCommand);
2396
3072
  program.addCommand(setupCommand);
2397
3073
  program.addCommand(configCommand);
3074
+ program.addCommand(memberCommand);
2398
3075
  program.addCommand(cacheCommand);
2399
3076
  program.addCommand(doctorCommand);
2400
3077
  program.addCommand(projectCommand);