@bifos/dooray-cli 0.4.0 → 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
@@ -195,23 +195,27 @@ var import_node_os2 = require("os");
195
195
  var CACHE_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".dooray", "cache");
196
196
  var ME_PATH = (0, import_node_path2.join)(CACHE_DIR, "me.json");
197
197
  var PROJECTS_PATH = (0, import_node_path2.join)(CACHE_DIR, "projects.json");
198
+ var PROJECTS_PRIVATE_PATH = (0, import_node_path2.join)(CACHE_DIR, "projects-private.json");
198
199
  var MEMBERS_DIR = (0, import_node_path2.join)(CACHE_DIR, "members");
199
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 WIKIS_PATH = (0, import_node_path2.join)(CACHE_DIR, "wikis.json");
200
204
  async function ensureDir2(dir) {
201
205
  await (0, import_promises2.mkdir)(dir, { recursive: true });
202
206
  }
203
- async function readJson(path) {
207
+ async function readJson(path3) {
204
208
  try {
205
- const raw = await (0, import_promises2.readFile)(path, "utf-8");
209
+ const raw = await (0, import_promises2.readFile)(path3, "utf-8");
206
210
  return JSON.parse(raw);
207
211
  } catch {
208
212
  return null;
209
213
  }
210
214
  }
211
- async function writeJson(path, data) {
212
- const dir = path.substring(0, path.lastIndexOf("/"));
215
+ async function writeJson(path3, data) {
216
+ const dir = path3.substring(0, path3.lastIndexOf("/"));
213
217
  await ensureDir2(dir);
214
- await (0, import_promises2.writeFile)(path, JSON.stringify(data, null, 2) + "\n");
218
+ await (0, import_promises2.writeFile)(path3, JSON.stringify(data, null, 2) + "\n");
215
219
  }
216
220
  function isExpired(updatedAt, ttlMs) {
217
221
  if (!updatedAt) return true;
@@ -232,6 +236,12 @@ async function getProjects() {
232
236
  async function setProjects(items) {
233
237
  await writeJson(PROJECTS_PATH, { updatedAt: now(), data: items });
234
238
  }
239
+ async function getPrivateProjects() {
240
+ return readJson(PROJECTS_PRIVATE_PATH);
241
+ }
242
+ async function setPrivateProjects(items) {
243
+ await writeJson(PROJECTS_PRIVATE_PATH, { updatedAt: now(), data: items });
244
+ }
235
245
  function membersPath(projectId) {
236
246
  return (0, import_node_path2.join)(MEMBERS_DIR, `${projectId}.json`);
237
247
  }
@@ -250,6 +260,30 @@ async function getWorkflows(projectId) {
250
260
  async function setWorkflows(projectId, items) {
251
261
  await writeJson(workflowsPath(projectId), { updatedAt: now(), data: items });
252
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
+ }
281
+ async function getWikis() {
282
+ return readJson(WIKIS_PATH);
283
+ }
284
+ async function setWikis(items) {
285
+ await writeJson(WIKIS_PATH, { updatedAt: now(), data: items });
286
+ }
253
287
  async function clearCache() {
254
288
  try {
255
289
  await (0, import_promises2.rm)(CACHE_DIR, { recursive: true, force: true });
@@ -271,8 +305,20 @@ async function getCacheStats() {
271
305
  workflowProjectCount = files.filter((f) => f.endsWith(".json")).length;
272
306
  } catch {
273
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
+ }
274
320
  const me = await getMe();
275
- return { projectCount, memberProjectCount, workflowProjectCount, me: me?.data ?? null };
321
+ return { projectCount, memberProjectCount, workflowProjectCount, tagProjectCount, milestoneProjectCount, me: me?.data ?? null };
276
322
  }
277
323
 
278
324
  // src/commands/cache.ts
@@ -289,11 +335,24 @@ cacheCommand.command("refresh").description("\uCE90\uC2DC \uAC31\uC2E0 (API \uD0
289
335
  // src/commands/doctor.ts
290
336
  var import_commander3 = require("commander");
291
337
  var import_chalk3 = __toESM(require("chalk"));
338
+ var import_path = __toESM(require("path"));
339
+ var import_promises4 = __toESM(require("fs/promises"));
292
340
 
293
341
  // src/api/client.ts
294
342
  var import_ky = __toESM(require("ky"));
295
343
  var import_promises3 = require("fs/promises");
296
344
  var import_node_path3 = require("path");
345
+
346
+ // src/utils/dooray-message.ts
347
+ function normalizeDoorayMessage(raw) {
348
+ try {
349
+ return decodeURIComponent(raw.replace(/\+/g, " "));
350
+ } catch {
351
+ return raw;
352
+ }
353
+ }
354
+
355
+ // src/api/client.ts
297
356
  function joinIds(ids) {
298
357
  return ids && ids.length > 0 ? ids.join(",") : void 0;
299
358
  }
@@ -304,7 +363,7 @@ async function toDoorayCliError(error) {
304
363
  try {
305
364
  const body = await error.response.json();
306
365
  throw new DoorayCliError(
307
- `API \uD638\uCD9C \uC2E4\uD328: ${body.header.resultMessage}`,
366
+ `API \uD638\uCD9C \uC2E4\uD328: ${normalizeDoorayMessage(body.header.resultMessage)}`,
308
367
  exitCode
309
368
  );
310
369
  } catch (e) {
@@ -390,6 +449,13 @@ var DoorayApiClient = class {
390
449
  return toDoorayCliError(e);
391
450
  }
392
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
+ }
393
459
  async createPost(projectId, body) {
394
460
  try {
395
461
  return await this.api.post(`project/v1/projects/${projectId}/posts`, { json: body }).json();
@@ -493,6 +559,32 @@ var DoorayApiClient = class {
493
559
  return toDoorayCliError(e);
494
560
  }
495
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
+ }
496
588
  // ─── Wiki ───────────────────────────────────────────
497
589
  async getWikis(params) {
498
590
  try {
@@ -538,6 +630,20 @@ var DoorayApiClient = class {
538
630
  return toDoorayCliError(e);
539
631
  }
540
632
  }
633
+ async updateWikiPageTitle(wikiId, pageId, body) {
634
+ try {
635
+ return await this.api.put(`wiki/v1/wikis/${wikiId}/pages/${pageId}/title`, { json: body }).json();
636
+ } catch (e) {
637
+ return toDoorayCliError(e);
638
+ }
639
+ }
640
+ async updateWikiPageContent(wikiId, pageId, body) {
641
+ try {
642
+ return await this.api.put(`wiki/v1/wikis/${wikiId}/pages/${pageId}/content`, { json: body }).json();
643
+ } catch (e) {
644
+ return toDoorayCliError(e);
645
+ }
646
+ }
541
647
  // ─── Post Files ─────────────────────────────────────
542
648
  async getPostFiles(projectId, postId) {
543
649
  try {
@@ -636,6 +742,10 @@ var PROJECTS_TTL_MS = 36e5;
636
742
  var MEMBERS_TTL_MS = 36e5;
637
743
  var WORKFLOWS_TTL_MS = 864e5;
638
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;
748
+ var WIKIS_TTL_MS = 864e5;
639
749
 
640
750
  // src/resolvers/me.ts
641
751
  async function ensureMe(client) {
@@ -675,6 +785,37 @@ var doctorCommand = new import_commander3.Command("doctor").description("\uC124\
675
785
  console.log(` \uD504\uB85C\uC81D\uD2B8: ${stats.projectCount}\uAC1C`);
676
786
  console.log(` \uBA64\uBC84: ${stats.memberProjectCount}\uAC1C \uD504\uB85C\uC81D\uD2B8`);
677
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}`);
790
+ const claudeDir = import_path.default.join(
791
+ process.env.HOME ?? process.env.USERPROFILE ?? "",
792
+ ".claude"
793
+ );
794
+ const claudeDirExists = await import_promises4.default.access(claudeDir).then(() => true).catch(() => false);
795
+ if (claudeDirExists) {
796
+ console.log(import_chalk3.default.bold("\n\u{1F527} Claude Code \uC2A4\uD0AC\n"));
797
+ const skillDst = import_path.default.join(claudeDir, "skills", "dooray-cli");
798
+ try {
799
+ const stat2 = await import_promises4.default.lstat(skillDst);
800
+ if (stat2.isSymbolicLink()) {
801
+ const target = await import_promises4.default.readlink(skillDst);
802
+ const targetExists = await import_promises4.default.access(target).then(() => true).catch(() => false);
803
+ if (targetExists) {
804
+ console.log(` dooray-cli: ${import_chalk3.default.green("\u2705 \uC124\uCE58\uB428 (\uC2EC\uBCFC\uB9AD \uB9C1\uD06C)")}`);
805
+ } else {
806
+ console.log(
807
+ ` dooray-cli: ${import_chalk3.default.yellow("\u26A0\uFE0F \uB9C1\uD06C \uAE68\uC9D0 \u2014 dooray setup\uC73C\uB85C \uC7AC\uC124\uCE58")}`
808
+ );
809
+ }
810
+ } else {
811
+ console.log(` dooray-cli: ${import_chalk3.default.green("\u2705 \uC124\uCE58\uB428 (\uBCF5\uC0AC\uBCF8)")}`);
812
+ }
813
+ } catch {
814
+ console.log(
815
+ ` dooray-cli: ${import_chalk3.default.red("\u274C \uBBF8\uC124\uCE58 \u2014 dooray setup\uC73C\uB85C \uC124\uCE58")}`
816
+ );
817
+ }
818
+ }
678
819
  console.log();
679
820
  if (apiKeyOk && baseUrlOk) {
680
821
  console.log(import_chalk3.default.green("\u2713 \uAE30\uBCF8 \uC124\uC815\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
@@ -688,6 +829,8 @@ var doctorCommand = new import_commander3.Command("doctor").description("\uC124\
688
829
  // src/commands/setup.ts
689
830
  var import_commander4 = require("commander");
690
831
  var import_chalk4 = __toESM(require("chalk"));
832
+ var import_path2 = __toESM(require("path"));
833
+ var import_promises5 = __toESM(require("fs/promises"));
691
834
  var setupCommand = new import_commander4.Command("setup").description("\uB300\uD654\uD615 \uCD08\uAE30 \uC124\uC815 \uB9C8\uBC95\uC0AC").action(async () => {
692
835
  const { input, select, password, confirm } = await import("@inquirer/prompts");
693
836
  const existing = await getConfig();
@@ -760,6 +903,45 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
760
903
  theme: { prefix: "\u{1F512}" }
761
904
  });
762
905
  }
906
+ const claudeDir = import_path2.default.join(
907
+ process.env.HOME ?? process.env.USERPROFILE ?? "",
908
+ ".claude"
909
+ );
910
+ const claudeDirExists = await import_promises5.default.access(claudeDir).then(() => true).catch(() => false);
911
+ if (claudeDirExists) {
912
+ const isNpx = /_npx[/\\]/.test(__dirname) || /\.npm[/\\]_npx/.test(__dirname) || /npx-/.test(__dirname);
913
+ if (isNpx) {
914
+ console.log(
915
+ import_chalk4.default.yellow(
916
+ " \u26A0 npx \uD658\uACBD\uC5D0\uC11C\uB294 \uC2A4\uD0AC \uC124\uCE58\uAC00 \uBD88\uAC00\uD569\uB2C8\uB2E4. npm i -g @bifos/dooray-cli \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."
917
+ )
918
+ );
919
+ } else {
920
+ const installSkill = await confirm({
921
+ message: "Claude Code \uC2A4\uD0AC\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
922
+ default: true,
923
+ theme: { prefix: "\u{1F527}" }
924
+ });
925
+ if (installSkill) {
926
+ try {
927
+ const skillSrc = import_path2.default.resolve(__dirname, "../skills/dooray-cli");
928
+ const skillsDir = import_path2.default.join(claudeDir, "skills");
929
+ const skillDst = import_path2.default.join(skillsDir, "dooray-cli");
930
+ await import_promises5.default.mkdir(skillsDir, { recursive: true });
931
+ await import_promises5.default.lstat(skillDst).then(() => import_promises5.default.rm(skillDst, { recursive: true, force: true })).catch(() => {
932
+ });
933
+ await import_promises5.default.symlink(skillSrc, skillDst);
934
+ console.log(import_chalk4.default.green(" \u2713 Claude Code \uC2A4\uD0AC \uC124\uCE58 \uC644\uB8CC"));
935
+ } catch (err) {
936
+ console.log(
937
+ import_chalk4.default.yellow(
938
+ ` \u26A0 \uC2A4\uD0AC \uC124\uCE58 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
939
+ )
940
+ );
941
+ }
942
+ }
943
+ }
944
+ }
763
945
  const config = {
764
946
  version: 1,
765
947
  apiKey,
@@ -791,12 +973,12 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
791
973
  var import_commander5 = require("commander");
792
974
 
793
975
  // src/resolvers/project.ts
794
- async function fetchAllProjects(client) {
976
+ async function fetchAllProjects(client, options) {
795
977
  const all = [];
796
978
  let page = 0;
797
979
  const size = 100;
798
980
  while (true) {
799
- const res = await client.getProjects({ page, size });
981
+ const res = await client.getProjects({ page, size, type: options?.type });
800
982
  for (const p of res.result) {
801
983
  all.push({
802
984
  id: p.id,
@@ -818,12 +1000,27 @@ async function ensureProjects(client) {
818
1000
  await setProjects(items);
819
1001
  return items;
820
1002
  }
1003
+ async function ensurePrivateProjects(client) {
1004
+ const entry = await getPrivateProjects();
1005
+ if (entry && !isExpired(entry.updatedAt, PROJECTS_TTL_MS)) {
1006
+ return entry.data;
1007
+ }
1008
+ const items = await fetchAllProjects(client, { type: "private" });
1009
+ await setPrivateProjects(items);
1010
+ return items;
1011
+ }
821
1012
  async function resolveProject(client, input) {
822
1013
  const projects = await ensureProjects(client);
823
1014
  const match = projects.find((p) => p.code === input || p.id === input);
824
1015
  if (match) return match.id;
1016
+ const privateCached = await getPrivateProjects();
1017
+ if (privateCached && !isExpired(privateCached.updatedAt, PROJECTS_TTL_MS)) {
1018
+ const privateMatch = privateCached.data.find((p) => p.code === input || p.id === input);
1019
+ if (privateMatch) return privateMatch.id;
1020
+ }
825
1021
  throw new DoorayCliError(
826
- `\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1022
+ `\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}
1023
+ \uAC1C\uC778 \uD504\uB85C\uC81D\uD2B8\uB77C\uBA74: dooray project list --type private \uB85C \uCE90\uC2DC\uB97C \uAC31\uC2E0\uD558\uC138\uC694`,
827
1024
  EXIT_PARAM_ERROR
828
1025
  );
829
1026
  }
@@ -871,13 +1068,16 @@ function stopSpinner(success, text) {
871
1068
  }
872
1069
 
873
1070
  // src/commands/project/list.ts
874
- var projectListCommand = new import_commander5.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C").option("-s, --search <keyword>", "code \uD544\uD130\uB9C1").action(async (opts) => {
1071
+ var projectListCommand = new import_commander5.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C").option("-s, --search <keyword>", "code \uD544\uD130\uB9C1").addOption(
1072
+ new import_commander5.Option("-t, --type <type>", "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785 \uD544\uD130").choices(["public", "private"]).default("public")
1073
+ ).action(async (opts) => {
875
1074
  const globalOpts = projectListCommand.optsWithGlobals();
876
1075
  const config = await getConfigOrThrow();
877
1076
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
878
- startSpinner("\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC911...");
879
- const projects = await ensureProjects(client);
880
- stopSpinner(true, "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1077
+ const isPrivate = opts.type === "private";
1078
+ startSpinner(isPrivate ? "\uAC1C\uC778 \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC911..." : "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC911...");
1079
+ const projects = isPrivate ? await ensurePrivateProjects(client) : await ensureProjects(client);
1080
+ stopSpinner(true, isPrivate ? "\uAC1C\uC778 \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC" : "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
881
1081
  let filtered = projects;
882
1082
  if (opts.search) {
883
1083
  const keyword = opts.search.toLowerCase();
@@ -894,6 +1094,32 @@ var projectListCommand = new import_commander5.Command("list").description("\uD5
894
1094
  // src/commands/project/members.ts
895
1095
  var import_commander6 = require("commander");
896
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
+
897
1123
  // src/resolvers/member.ts
898
1124
  async function fetchAllMembers(client, projectId) {
899
1125
  const memberIds = [];
@@ -935,22 +1161,23 @@ async function ensureMembers(client, projectId) {
935
1161
  await setMembers(projectId, items);
936
1162
  return items;
937
1163
  }
938
- async function resolveMember(client, projectId, input) {
1164
+ async function buildMemberNameMap(client, projectId) {
939
1165
  const members = await ensureMembers(client, projectId);
940
- const byName = members.filter((m) => m.name.includes(input));
941
- if (byName.length === 1) return byName[0].organizationMemberId;
942
- if (byName.length > 1) {
943
- const candidates = byName.map((m) => ` - ${m.name} (${m.organizationMemberId})`).join("\n");
944
- throw new DoorayCliError(
945
- `\uBCF5\uC218\uC758 \uBA64\uBC84\uAC00 \uB9E4\uCE6D\uB429\uB2C8\uB2E4: "${input}"
946
- ${candidates}`,
947
- EXIT_PARAM_ERROR
948
- );
1166
+ const map = /* @__PURE__ */ new Map();
1167
+ for (const m of members) {
1168
+ if (m.name) map.set(m.organizationMemberId, m.name);
949
1169
  }
950
- throw new DoorayCliError(
951
- `\uBA64\uBC84\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
952
- 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})`
953
1179
  );
1180
+ return match.organizationMemberId;
954
1181
  }
955
1182
 
956
1183
  // src/commands/project/members.ts
@@ -994,12 +1221,16 @@ async function ensureWorkflows(client, projectId) {
994
1221
  }
995
1222
  async function resolveWorkflow(client, projectId, input) {
996
1223
  const workflows = await ensureWorkflows(client, projectId);
997
- const match = workflows.find((w) => w.name === input || w.class === input);
998
- if (match) return match.id;
999
- throw new DoorayCliError(
1000
- `\uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}`,
1001
- EXIT_PARAM_ERROR
1002
- );
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;
1003
1234
  }
1004
1235
 
1005
1236
  // src/commands/project/workflows.ts
@@ -1139,14 +1370,95 @@ async function resolvePost(client, projectId, postNumber) {
1139
1370
  return res.result[0].id;
1140
1371
  }
1141
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
+
1142
1450
  // src/commands/post/get.ts
1143
- 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) => {
1144
1452
  const globalOpts = postGetCommand.optsWithGlobals();
1145
1453
  const config = await getConfigOrThrow();
1146
1454
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1147
1455
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
1148
- const projectId = await resolveProject(client, project);
1149
- 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
+ });
1150
1462
  const res = await client.getPost(projectId, postId);
1151
1463
  stopSpinner(true, "\uC5C5\uBB34 \uC870\uD68C \uC644\uB8CC");
1152
1464
  formatPostDetail(res.result, globalOpts);
@@ -1154,11 +1466,10 @@ var postGetCommand = new import_commander10.Command("get").description("\uC5C5\u
1154
1466
 
1155
1467
  // src/commands/post/edit.ts
1156
1468
  var import_commander11 = require("commander");
1157
- var import_promises5 = __toESM(require("fs/promises"));
1158
1469
 
1159
1470
  // src/editor/index.ts
1160
1471
  var import_node_child_process = require("child_process");
1161
- var import_promises4 = __toESM(require("fs/promises"));
1472
+ var import_promises6 = __toESM(require("fs/promises"));
1162
1473
  var import_tmp = __toESM(require("tmp"));
1163
1474
  var import_js_yaml = __toESM(require("js-yaml"));
1164
1475
  function openInEditor(content) {
@@ -1172,7 +1483,7 @@ function openInEditor(content) {
1172
1483
  const tmpFile = import_tmp.default.fileSync({ prefix: "dooray-", postfix: ".md" });
1173
1484
  return new Promise(async (resolve, reject) => {
1174
1485
  try {
1175
- await import_promises4.default.writeFile(tmpFile.name, content, "utf-8");
1486
+ await import_promises6.default.writeFile(tmpFile.name, content, "utf-8");
1176
1487
  const child = (0, import_node_child_process.spawn)(editor, [tmpFile.name], {
1177
1488
  stdio: "inherit"
1178
1489
  });
@@ -1188,7 +1499,7 @@ function openInEditor(content) {
1188
1499
  EXIT_PARAM_ERROR
1189
1500
  );
1190
1501
  }
1191
- const result = await import_promises4.default.readFile(tmpFile.name, "utf-8");
1502
+ const result = await import_promises6.default.readFile(tmpFile.name, "utf-8");
1192
1503
  resolve(result);
1193
1504
  } catch (e) {
1194
1505
  reject(e);
@@ -1281,14 +1592,25 @@ function parseWikiFrontmatter(content) {
1281
1592
  };
1282
1593
  }
1283
1594
 
1284
- // src/commands/post/edit.ts
1285
- async function resolveUsers(client, projectId, emails) {
1286
- const users = [];
1287
- for (const email of emails) {
1288
- const memberId = await resolveMember(client, projectId, email);
1289
- users.push({ type: "member", member: { organizationMemberId: memberId } });
1595
+ // src/utils/body-input.ts
1596
+ var import_promises7 = require("fs/promises");
1597
+ async function readBodyInput(opts) {
1598
+ if (opts.body != null && opts.bodyFile != null) {
1599
+ throw new DoorayCliError(
1600
+ "--body\uC640 --body-file\uC740 \uD568\uAED8 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
1601
+ EXIT_PARAM_ERROR
1602
+ );
1290
1603
  }
1291
- return users;
1604
+ if (opts.bodyFile) {
1605
+ if (opts.bodyFile === "-") return readStdin();
1606
+ return (0, import_promises7.readFile)(opts.bodyFile, "utf-8");
1607
+ }
1608
+ if (opts.body === "-") return readStdin();
1609
+ return opts.body ?? "";
1610
+ }
1611
+ async function readBodyInputOrNull(opts) {
1612
+ if (opts.body == null && opts.bodyFile == null) return null;
1613
+ return readBodyInput(opts);
1292
1614
  }
1293
1615
  async function readStdin() {
1294
1616
  if (process.stdin.isTTY) {
@@ -1303,30 +1625,39 @@ async function readStdin() {
1303
1625
  }
1304
1626
  return Buffer.concat(chunks).toString("utf-8");
1305
1627
  }
1306
- async function resolveBody(opts) {
1307
- if (opts.body) {
1308
- if (opts.body === "-") return readStdin();
1309
- return opts.body;
1310
- }
1311
- if (opts.bodyFile) {
1312
- if (opts.bodyFile === "-") return readStdin();
1313
- return import_promises5.default.readFile(opts.bodyFile, "utf-8");
1628
+
1629
+ // src/commands/post/edit.ts
1630
+ async function resolveUsers(client, projectId, emails) {
1631
+ const users = [];
1632
+ for (const email of emails) {
1633
+ const memberId = await resolveMember(client, projectId, email);
1634
+ users.push({ type: "member", member: { organizationMemberId: memberId } });
1314
1635
  }
1315
- return null;
1636
+ return users;
1316
1637
  }
1317
- var postEditCommand = new import_commander11.Command("edit").description("\uC5C5\uBB34 \uC218\uC815 ($EDITOR \uB610\uB294 --subject/--body \uC635\uC158)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<post-number>", "\uC5C5\uBB34 \uBC88\uD638").option("--subject <title>", "\uC81C\uBAA9 \uBCC0\uACBD (non-interactive)").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) => {
1318
1639
  const config = await getConfigOrThrow();
1319
1640
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1320
1641
  startSpinner("\uC5C5\uBB34 \uC870\uD68C \uC911...");
1321
- const projectId = await resolveProject(client, project);
1322
- 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
+ });
1323
1648
  const res = await client.getPost(projectId, postId);
1324
1649
  const post = res.result;
1325
1650
  const members = await ensureMembers(client, projectId);
1326
1651
  stopSpinner(true, "\uC5C5\uBB34 \uC870\uD68C \uC644\uB8CC");
1327
- const nonInteractive = opts.subject || opts.body || opts.bodyFile;
1652
+ const title = opts.title ?? opts.subject;
1653
+ if (opts.subject && !opts.title) {
1654
+ process.stderr.write(
1655
+ "\u26A0 --subject\uB294 deprecated\uC785\uB2C8\uB2E4. \uB300\uC2E0 --title\uC744 \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694.\n"
1656
+ );
1657
+ }
1658
+ const nonInteractive = title || opts.body || opts.bodyFile;
1328
1659
  if (nonInteractive) {
1329
- const newBody = await resolveBody(opts);
1660
+ const newBody = await readBodyInputOrNull(opts);
1330
1661
  startSpinner("\uC5C5\uBB34 \uC218\uC815 \uC911...");
1331
1662
  const toUsers = post.users.to.map((u) => ({
1332
1663
  type: u.type,
@@ -1341,7 +1672,7 @@ var postEditCommand = new import_commander11.Command("edit").description("\uC5C5
1341
1672
  group: u.group
1342
1673
  }));
1343
1674
  await client.updatePost(projectId, postId, {
1344
- subject: opts.subject ?? post.subject,
1675
+ subject: title ?? post.subject,
1345
1676
  body: {
1346
1677
  mimeType: "text/x-markdown",
1347
1678
  content: newBody ?? post.body.content
@@ -1373,38 +1704,137 @@ var postEditCommand = new import_commander11.Command("edit").description("\uC5C5
1373
1704
  });
1374
1705
  stopSpinner(true, "\uC5C5\uBB34 \uC218\uC815 \uC644\uB8CC");
1375
1706
  }
1376
- 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.
1377
1708
  `);
1378
1709
  });
1379
1710
 
1380
1711
  // src/commands/post/create.ts
1381
1712
  var import_commander12 = require("commander");
1382
- var import_promises6 = __toESM(require("fs/promises"));
1383
- async function readBody(opts) {
1384
- if (opts.bodyFile) {
1385
- if (opts.bodyFile === "-") {
1386
- return readStdin2();
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
+ });
1387
1730
  }
1388
- return import_promises6.default.readFile(opts.bodyFile, "utf-8");
1389
- }
1390
- if (opts.body === "-") {
1391
- return readStdin2();
1731
+ const total = res.totalCount ?? all.length;
1732
+ if (all.length >= total) break;
1733
+ page++;
1392
1734
  }
1393
- return "";
1735
+ return all;
1394
1736
  }
1395
- async function readStdin2() {
1396
- if (process.stdin.isTTY) {
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) {
1397
1764
  throw new DoorayCliError(
1398
- "stdin\uC5D0\uC11C \uC77D\uC73C\uB824\uBA74 \uD30C\uC774\uD504\uB85C \uB370\uC774\uD130\uB97C \uC804\uB2EC\uD574\uC8FC\uC138\uC694.",
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"),
1399
1767
  EXIT_PARAM_ERROR
1400
1768
  );
1401
1769
  }
1402
- const chunks = [];
1403
- for await (const chunk of process.stdin) {
1404
- chunks.push(chunk);
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
+ }
1405
1782
  }
1406
- return Buffer.concat(chunks).toString("utf-8");
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;
1407
1835
  }
1836
+
1837
+ // src/commands/post/create.ts
1408
1838
  async function resolveUsers2(client, projectId, inputs) {
1409
1839
  const users = [];
1410
1840
  for (const input of inputs) {
@@ -1413,23 +1843,54 @@ async function resolveUsers2(client, projectId, inputs) {
1413
1843
  }
1414
1844
  return users;
1415
1845
  }
1416
- var postCreateCommand = new import_commander12.Command("create").description("\uC5C5\uBB34 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--subject <title>", "\uC5C5\uBB34 \uC81C\uBAA9").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) => {
1417
1847
  const globalOpts = postCreateCommand.optsWithGlobals();
1418
1848
  const config = await getConfigOrThrow();
1419
1849
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1420
- const bodyContent = await readBody(opts);
1850
+ const subject = opts.title ?? opts.subject;
1851
+ if (!subject) {
1852
+ throw new DoorayCliError(
1853
+ "--title\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
1854
+ EXIT_PARAM_ERROR
1855
+ );
1856
+ }
1857
+ if (opts.subject && !opts.title) {
1858
+ process.stderr.write(
1859
+ "\u26A0 --subject\uB294 deprecated\uC785\uB2C8\uB2E4. \uB300\uC2E0 --title\uC744 \uC0AC\uC6A9\uD574\uC8FC\uC138\uC694.\n"
1860
+ );
1861
+ }
1862
+ const bodyContent = await readBodyInput(opts);
1421
1863
  startSpinner("\uC5C5\uBB34 \uC0DD\uC131 \uC911...");
1422
1864
  const projectId = await resolveProject(client, project);
1423
1865
  const toUsers = opts.to ? await resolveUsers2(client, projectId, opts.to) : [];
1424
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
+ ]);
1425
1873
  const res = await client.createPost(projectId, {
1426
- subject: opts.subject,
1874
+ subject,
1427
1875
  body: { mimeType: "text/x-markdown", content: bodyContent },
1428
1876
  users: { to: toUsers, cc: ccUsers },
1429
1877
  priority: opts.priority,
1430
- ...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 }
1431
1882
  });
1432
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
+ }
1433
1894
  if (globalOpts.json) {
1434
1895
  printJson(res.result);
1435
1896
  } else if (globalOpts.quiet) {
@@ -1442,82 +1903,120 @@ var postCreateCommand = new import_commander12.Command("create").description("\u
1442
1903
 
1443
1904
  // src/commands/post/done.ts
1444
1905
  var import_commander13 = require("commander");
1445
- 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) => {
1446
1907
  const config = await getConfigOrThrow();
1447
1908
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1448
1909
  startSpinner("\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC \uC911...");
1449
- const projectId = await resolveProject(client, project);
1450
- 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
+ });
1451
1916
  await client.setPostDone(projectId, postId);
1452
1917
  stopSpinner(true, "\uC5C5\uBB34 \uC644\uB8CC \uCC98\uB9AC \uC644\uB8CC");
1453
- 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.
1454
1919
  `);
1455
1920
  });
1456
1921
 
1457
1922
  // src/commands/post/workflow.ts
1458
1923
  var import_commander14 = require("commander");
1459
- 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) => {
1460
1925
  const config = await getConfigOrThrow();
1461
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
+ }
1462
1949
  startSpinner("\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD \uC911...");
1463
- const projectId = await resolveProject(client, project);
1464
- const postId = await resolvePost(client, projectId, Number(postNumberStr));
1465
- 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);
1466
1957
  await client.setPostWorkflow(projectId, postId, workflowId);
1467
1958
  stopSpinner(true, "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBCC0\uACBD \uC644\uB8CC");
1468
- 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.
1469
1960
  `);
1470
1961
  });
1471
1962
 
1472
1963
  // src/commands/post/comment/list.ts
1473
1964
  var import_commander15 = require("commander");
1474
- 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) => {
1475
1986
  const globalOpts = commentListCommand.optsWithGlobals();
1476
1987
  const config = await getConfigOrThrow();
1477
1988
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1478
1989
  startSpinner("\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC911...");
1479
- const projectId = await resolveProject(client, project);
1480
- 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
+ });
1481
1996
  const res = await client.getPostComments(projectId, postId, {
1482
1997
  page: Number(opts.page),
1483
1998
  size: Number(opts.size)
1484
1999
  });
1485
2000
  stopSpinner(true, "\uB313\uAE00 \uBAA9\uB85D \uC870\uD68C \uC644\uB8CC");
1486
- 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);
1487
2011
  });
1488
2012
 
1489
2013
  // src/commands/post/comment/add.ts
1490
2014
  var import_commander16 = require("commander");
1491
- var import_promises7 = __toESM(require("fs/promises"));
1492
- async function readStdin3() {
1493
- if (process.stdin.isTTY) {
1494
- throw new DoorayCliError(
1495
- "stdin\uC5D0\uC11C \uC77D\uC73C\uB824\uBA74 \uD30C\uC774\uD504\uB85C \uB370\uC774\uD130\uB97C \uC804\uB2EC\uD574\uC8FC\uC138\uC694.",
1496
- EXIT_PARAM_ERROR
1497
- );
1498
- }
1499
- const chunks = [];
1500
- for await (const chunk of process.stdin) {
1501
- chunks.push(chunk);
1502
- }
1503
- return Buffer.concat(chunks).toString("utf-8");
1504
- }
1505
- async function resolveBody2(opts) {
1506
- if (opts.body) {
1507
- if (opts.body === "-") return readStdin3();
1508
- return opts.body;
1509
- }
1510
- if (opts.bodyFile) {
1511
- if (opts.bodyFile === "-") return readStdin3();
1512
- return import_promises7.default.readFile(opts.bodyFile, "utf-8");
1513
- }
1514
- return null;
1515
- }
1516
- 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) => {
1517
2016
  const globalOpts = commentAddCommand.optsWithGlobals();
1518
2017
  const config = await getConfigOrThrow();
1519
2018
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1520
- let bodyContent = await resolveBody2(opts);
2019
+ let bodyContent = await readBodyInputOrNull(opts);
1521
2020
  if (bodyContent == null) {
1522
2021
  bodyContent = await openInEditor("");
1523
2022
  if (!bodyContent.trim()) {
@@ -1526,8 +2025,12 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
1526
2025
  }
1527
2026
  }
1528
2027
  startSpinner("\uB313\uAE00 \uCD94\uAC00 \uC911...");
1529
- const projectId = await resolveProject(client, project);
1530
- 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
+ });
1531
2034
  const res = await client.createPostComment(projectId, postId, {
1532
2035
  body: { mimeType: "text/x-markdown", content: bodyContent }
1533
2036
  });
@@ -1544,37 +2047,55 @@ var commentAddCommand = new import_commander16.Command("add").description("\uB31
1544
2047
 
1545
2048
  // src/commands/post/comment/edit.ts
1546
2049
  var import_commander17 = require("commander");
1547
- var import_promises8 = __toESM(require("fs/promises"));
1548
- async function readStdin4() {
1549
- if (process.stdin.isTTY) {
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) => {
2051
+ const config = await getConfigOrThrow();
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) {
1550
2087
  throw new DoorayCliError(
1551
- "stdin\uC5D0\uC11C \uC77D\uC73C\uB824\uBA74 \uD30C\uC774\uD504\uB85C \uB370\uC774\uD130\uB97C \uC804\uB2EC\uD574\uC8FC\uC138\uC694.",
2088
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
1552
2089
  EXIT_PARAM_ERROR
1553
2090
  );
1554
2091
  }
1555
- const chunks = [];
1556
- for await (const chunk of process.stdin) {
1557
- chunks.push(chunk);
1558
- }
1559
- return Buffer.concat(chunks).toString("utf-8");
1560
- }
1561
- async function resolveBody3(opts) {
1562
- if (opts.body) {
1563
- if (opts.body === "-") return readStdin4();
1564
- return opts.body;
1565
- }
1566
- if (opts.bodyFile) {
1567
- if (opts.bodyFile === "-") return readStdin4();
1568
- return import_promises8.default.readFile(opts.bodyFile, "utf-8");
1569
- }
1570
- return null;
1571
- }
1572
- 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) => {
1573
- const config = await getConfigOrThrow();
1574
- const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1575
2092
  startSpinner("\uB313\uAE00 \uC870\uD68C \uC911...");
1576
- const projectId = await resolveProject(client, project);
1577
- 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
+ });
1578
2099
  const comments = await client.getPostComments(projectId, postId);
1579
2100
  const comment = comments.result.find((c) => c.id === commentId);
1580
2101
  stopSpinner(true, "\uB313\uAE00 \uC870\uD68C \uC644\uB8CC");
@@ -1583,7 +2104,7 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
1583
2104
  `);
1584
2105
  process.exit(1);
1585
2106
  }
1586
- let edited = await resolveBody3(opts);
2107
+ let edited = await readBodyInputOrNull(opts);
1587
2108
  if (edited == null) {
1588
2109
  const original = comment.body.content;
1589
2110
  edited = await openInEditor(original);
@@ -1603,12 +2124,55 @@ var commentEditCommand = new import_commander17.Command("edit").description("\uB
1603
2124
 
1604
2125
  // src/commands/post/comment/delete.ts
1605
2126
  var import_commander18 = require("commander");
1606
- 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) => {
1607
2128
  const config = await getConfigOrThrow();
1608
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
+ }
1609
2169
  startSpinner("\uB313\uAE00 \uC0AD\uC81C \uC911...");
1610
- const projectId = await resolveProject(client, project);
1611
- 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
+ });
1612
2176
  await client.deletePostComment(projectId, postId, commentId);
1613
2177
  stopSpinner(true, "\uB313\uAE00 \uC0AD\uC81C \uC644\uB8CC");
1614
2178
  process.stdout.write(`\uB313\uAE00\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${commentId}
@@ -1622,13 +2186,17 @@ function formatSize(bytes) {
1622
2186
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
1623
2187
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1624
2188
  }
1625
- 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) => {
1626
2190
  const globalOpts = fileListCommand.optsWithGlobals();
1627
2191
  const config = await getConfigOrThrow();
1628
2192
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1629
2193
  startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
1630
- const projectId = await resolveProject(client, project);
1631
- 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
+ });
1632
2200
  const res = await client.getPostFiles(projectId, postId);
1633
2201
  stopSpinner(true, `\uCCA8\uBD80\uD30C\uC77C ${res.result.length}\uAC1C`);
1634
2202
  output(globalOpts, {
@@ -1647,17 +2215,57 @@ var fileListCommand = new import_commander19.Command("list").description("\uC5C5
1647
2215
 
1648
2216
  // src/commands/post/file/download.ts
1649
2217
  var import_commander20 = require("commander");
1650
- var import_promises9 = require("fs/promises");
2218
+ var import_promises8 = require("fs/promises");
1651
2219
  var import_node_path4 = require("path");
1652
- 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) => {
1653
2221
  const config = await getConfigOrThrow();
1654
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
+ }
1655
2259
  startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
1656
- const projectId = await resolveProject(client, project);
1657
- 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
+ });
1658
2266
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
1659
2267
  const outputPath = (0, import_node_path4.join)(opts.output, fileName);
1660
- await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
2268
+ await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
1661
2269
  stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
1662
2270
  process.stdout.write(`${outputPath}
1663
2271
  `);
@@ -1665,32 +2273,36 @@ var fileDownloadCommand = new import_commander20.Command("download").description
1665
2273
 
1666
2274
  // src/commands/post/file/download-all.ts
1667
2275
  var import_commander21 = require("commander");
1668
- var import_promises10 = require("fs/promises");
2276
+ var import_promises9 = require("fs/promises");
1669
2277
  var import_node_path5 = require("path");
1670
- 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) => {
1671
2279
  const config = await getConfigOrThrow();
1672
2280
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1673
2281
  const spinner = startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
1674
- const projectId = await resolveProject(client, project);
1675
- 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
+ });
1676
2288
  const res = await client.getPostFiles(projectId, postId);
1677
2289
  if (res.result.length === 0) {
1678
2290
  stopSpinner(true, "\uCCA8\uBD80\uD30C\uC77C \uC5C6\uC74C");
1679
2291
  process.stdout.write("\uCCA8\uBD80\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
1680
2292
  return;
1681
2293
  }
1682
- await (0, import_promises10.mkdir)(opts.output, { recursive: true });
2294
+ await (0, import_promises9.mkdir)(opts.output, { recursive: true });
1683
2295
  const downloaded = [];
1684
2296
  for (const file of res.result) {
1685
2297
  spinner.text = `\uB2E4\uC6B4\uB85C\uB4DC \uC911: ${file.name} (${downloaded.length + 1}/${res.result.length})`;
1686
2298
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, file.id);
1687
2299
  const outputPath = (0, import_node_path5.join)(opts.output, fileName);
1688
- await (0, import_promises10.writeFile)(outputPath, Buffer.from(buffer));
2300
+ await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
1689
2301
  downloaded.push(outputPath);
1690
2302
  }
1691
2303
  stopSpinner(true, `${downloaded.length}\uAC1C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC`);
1692
- for (const path of downloaded) {
1693
- process.stdout.write(`${path}
2304
+ for (const path3 of downloaded) {
2305
+ process.stdout.write(`${path3}
1694
2306
  `);
1695
2307
  }
1696
2308
  });
@@ -1698,13 +2310,53 @@ var fileDownloadAllCommand = new import_commander21.Command("download-all").desc
1698
2310
  // src/commands/post/file/upload.ts
1699
2311
  var import_commander22 = require("commander");
1700
2312
  var import_node_path6 = require("path");
1701
- 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) => {
1702
2314
  const globalOpts = fileUploadCommand.optsWithGlobals();
1703
2315
  const config = await getConfigOrThrow();
1704
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
+ }
1705
2353
  startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
1706
- const projectId = await resolveProject(client, project);
1707
- 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
+ });
1708
2360
  const res = await client.uploadPostFile(projectId, postId, filePath);
1709
2361
  stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
1710
2362
  if (globalOpts.json) {
@@ -1720,12 +2372,52 @@ var fileUploadCommand = new import_commander22.Command("upload").description("\u
1720
2372
 
1721
2373
  // src/commands/post/file/delete.ts
1722
2374
  var import_commander23 = require("commander");
1723
- 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) => {
1724
2376
  const config = await getConfigOrThrow();
1725
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
+ }
1726
2414
  startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
1727
- const projectId = await resolveProject(client, project);
1728
- 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
+ });
1729
2421
  await client.deletePostFile(projectId, postId, fileId);
1730
2422
  stopSpinner(true, "\uC0AD\uC81C \uC644\uB8CC");
1731
2423
  process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
@@ -1807,6 +2499,31 @@ async function resolveWiki(client, projectCode) {
1807
2499
  }
1808
2500
  return project.wikiId;
1809
2501
  }
2502
+ async function resolveWikiHomePageId(client, wikiId) {
2503
+ const cached = await getWikis();
2504
+ const fresh = cached && !isExpired(cached.updatedAt, WIKIS_TTL_MS);
2505
+ let wikis;
2506
+ if (fresh) {
2507
+ wikis = cached.data;
2508
+ } else {
2509
+ const res = await client.getWikis({ size: 100 });
2510
+ wikis = res.result.map((w) => ({
2511
+ id: w.id,
2512
+ projectId: w.project.id,
2513
+ name: w.name,
2514
+ homePageId: w.home.pageId
2515
+ }));
2516
+ await setWikis(wikis);
2517
+ }
2518
+ const wiki = wikis.find((w) => w.id === wikiId);
2519
+ if (!wiki?.homePageId) {
2520
+ throw new DoorayCliError(
2521
+ `\uC704\uD0A4\uC758 home \uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (wikiId: ${wikiId})`,
2522
+ EXIT_API_ERROR
2523
+ );
2524
+ }
2525
+ return wiki.homePageId;
2526
+ }
1810
2527
 
1811
2528
  // src/commands/wiki/pages.ts
1812
2529
  var wikiPagesCommand = new import_commander25.Command("pages").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").action(async (project, opts) => {
@@ -1835,43 +2552,18 @@ var wikiPageGetCommand = new import_commander26.Command("get").description("\uC7
1835
2552
 
1836
2553
  // src/commands/wiki/page-create.ts
1837
2554
  var import_commander27 = require("commander");
1838
- var import_promises11 = __toESM(require("fs/promises"));
1839
- async function readBody2(opts) {
1840
- if (opts.bodyFile) {
1841
- if (opts.bodyFile === "-") {
1842
- return readStdin5();
1843
- }
1844
- return import_promises11.default.readFile(opts.bodyFile, "utf-8");
1845
- }
1846
- if (opts.body === "-") {
1847
- return readStdin5();
1848
- }
1849
- return "";
1850
- }
1851
- async function readStdin5() {
1852
- if (process.stdin.isTTY) {
1853
- throw new DoorayCliError(
1854
- "stdin\uC5D0\uC11C \uC77D\uC73C\uB824\uBA74 \uD30C\uC774\uD504\uB85C \uB370\uC774\uD130\uB97C \uC804\uB2EC\uD574\uC8FC\uC138\uC694.",
1855
- EXIT_PARAM_ERROR
1856
- );
1857
- }
1858
- const chunks = [];
1859
- for await (const chunk of process.stdin) {
1860
- chunks.push(chunk);
1861
- }
1862
- return Buffer.concat(chunks).toString("utf-8");
1863
- }
1864
2555
  var wikiPageCreateCommand = new import_commander27.Command("create").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, opts) => {
1865
2556
  const globalOpts = wikiPageCreateCommand.optsWithGlobals();
1866
2557
  const config = await getConfigOrThrow();
1867
2558
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1868
- const bodyContent = await readBody2(opts);
2559
+ const bodyContent = await readBodyInput(opts);
1869
2560
  startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131 \uC911...");
1870
2561
  const wikiId = await resolveWiki(client, project);
2562
+ const parentPageId = opts.parent ?? await resolveWikiHomePageId(client, wikiId);
1871
2563
  const res = await client.createWikiPage(wikiId, {
1872
2564
  subject: opts.title,
1873
2565
  body: { mimeType: "text/x-markdown", content: bodyContent },
1874
- parentPageId: opts.parent ?? ""
2566
+ parentPageId
1875
2567
  });
1876
2568
  stopSpinner(true, "\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131 \uC644\uB8CC");
1877
2569
  if (globalOpts.json) {
@@ -1886,34 +2578,126 @@ var wikiPageCreateCommand = new import_commander27.Command("create").description
1886
2578
 
1887
2579
  // src/commands/wiki/page-edit.ts
1888
2580
  var import_commander28 = require("commander");
1889
- var wikiPageEditCommand = new import_commander28.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 ($EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
2581
+ var wikiPageEditCommand = new import_commander28.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 (\uD50C\uB798\uADF8 \uC5C6\uC73C\uBA74 $EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").option("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9 (\uC9C0\uC815 \uC2DC $EDITOR \uC0DD\uB7B5)").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, pageId, opts) => {
1890
2582
  const config = await getConfigOrThrow();
1891
2583
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
1892
- startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC870\uD68C \uC911...");
2584
+ const hasTitle = opts.title != null;
2585
+ const hasBody = opts.body != null || opts.bodyFile != null;
2586
+ const nonInteractive = hasTitle || hasBody;
2587
+ startSpinner("\uC704\uD0A4 \uC815\uBCF4 \uC870\uD68C \uC911...");
1893
2588
  const wikiId = await resolveWiki(client, project);
1894
- const res = await client.getWikiPage(wikiId, pageId);
1895
- const page = res.result;
1896
- stopSpinner(true, "\uC704\uD0A4 \uD398\uC774\uC9C0 \uC870\uD68C \uC644\uB8CC");
1897
- const original = serializeWikiFrontmatter(page);
1898
- const edited = await openInEditor(original);
1899
- if (original === edited) {
1900
- process.stdout.write("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C\n");
2589
+ if (!nonInteractive) {
2590
+ const res = await client.getWikiPage(wikiId, pageId);
2591
+ const page = res.result;
2592
+ stopSpinner(true, "\uC704\uD0A4 \uD398\uC774\uC9C0 \uC870\uD68C \uC644\uB8CC");
2593
+ const original = serializeWikiFrontmatter(page);
2594
+ const edited = await openInEditor(original);
2595
+ if (original === edited) {
2596
+ process.stdout.write("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C\n");
2597
+ return;
2598
+ }
2599
+ const parsed = parseWikiFrontmatter(edited);
2600
+ startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 \uC911...");
2601
+ await client.updateWikiPage(wikiId, pageId, {
2602
+ subject: parsed.title,
2603
+ body: { mimeType: "text/x-markdown", content: parsed.body }
2604
+ });
2605
+ stopSpinner(true, "\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 \uC644\uB8CC");
2606
+ process.stdout.write(`\uC704\uD0A4 \uD398\uC774\uC9C0\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${pageId}
2607
+ `);
1901
2608
  return;
1902
2609
  }
1903
- const parsed = parseWikiFrontmatter(edited);
1904
- startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 \uC911...");
1905
- await client.updateWikiPage(wikiId, pageId, {
1906
- subject: parsed.title,
1907
- body: { mimeType: "text/x-markdown", content: parsed.body }
1908
- });
2610
+ stopSpinner(true, "\uC704\uD0A4 \uC815\uBCF4 \uC870\uD68C \uC644\uB8CC");
2611
+ if (hasTitle && hasBody) {
2612
+ const bodyContent = await readBodyInput(opts);
2613
+ startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 \uC911...");
2614
+ await client.updateWikiPage(wikiId, pageId, {
2615
+ subject: opts.title,
2616
+ body: { mimeType: "text/x-markdown", content: bodyContent }
2617
+ });
2618
+ } else if (hasTitle) {
2619
+ startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC81C\uBAA9 \uC218\uC815 \uC911...");
2620
+ await client.updateWikiPageTitle(wikiId, pageId, { subject: opts.title });
2621
+ } else {
2622
+ const bodyContent = await readBodyInput(opts);
2623
+ startSpinner("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBCF8\uBB38 \uC218\uC815 \uC911...");
2624
+ await client.updateWikiPageContent(wikiId, pageId, {
2625
+ body: { mimeType: "text/x-markdown", content: bodyContent }
2626
+ });
2627
+ }
1909
2628
  stopSpinner(true, "\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 \uC644\uB8CC");
1910
2629
  process.stdout.write(`\uC704\uD0A4 \uD398\uC774\uC9C0\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${pageId}
1911
2630
  `);
1912
2631
  });
1913
2632
 
1914
- // src/commands/mail/list.ts
2633
+ // src/commands/member/index.ts
2634
+ var import_commander31 = require("commander");
2635
+
2636
+ // src/commands/member/get.ts
1915
2637
  var import_commander29 = require("commander");
1916
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
+
1917
2701
  // src/api/imapClient.ts
1918
2702
  var import_imapflow = require("imapflow");
1919
2703
  var import_mailparser = require("mailparser");
@@ -2020,7 +2804,7 @@ async function getMail(config, uid) {
2020
2804
 
2021
2805
  // src/commands/mail/list.ts
2022
2806
  var import_chalk5 = __toESM(require("chalk"));
2023
- 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) => {
2024
2808
  const globalOpts = mailListCommand.optsWithGlobals();
2025
2809
  const config = await getConfigOrThrow();
2026
2810
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2052,9 +2836,9 @@ var mailListCommand = new import_commander29.Command("list").description("\uBA54
2052
2836
  });
2053
2837
 
2054
2838
  // src/commands/mail/get.ts
2055
- var import_commander30 = require("commander");
2839
+ var import_commander33 = require("commander");
2056
2840
  var import_chalk6 = __toESM(require("chalk"));
2057
- 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) => {
2058
2842
  const globalOpts = mailGetCommand.optsWithGlobals();
2059
2843
  const config = await getConfigOrThrow();
2060
2844
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -2085,8 +2869,8 @@ ${mail.body}
2085
2869
  });
2086
2870
 
2087
2871
  // src/commands/mail/send.ts
2088
- var import_commander31 = require("commander");
2089
- var import_promises12 = require("fs/promises");
2872
+ var import_commander34 = require("commander");
2873
+ var import_promises10 = require("fs/promises");
2090
2874
 
2091
2875
  // src/api/smtpClient.ts
2092
2876
  var import_nodemailer = __toESM(require("nodemailer"));
@@ -2135,12 +2919,12 @@ async function sendMail(config, opts) {
2135
2919
  }
2136
2920
 
2137
2921
  // src/commands/mail/send.ts
2138
- 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) => {
2139
2923
  const globalOpts = mailSendCommand.optsWithGlobals();
2140
2924
  const config = await getConfigOrThrow();
2141
2925
  let body = opts.body ?? "";
2142
2926
  if (opts.bodyFile) {
2143
- body = await (0, import_promises12.readFile)(opts.bodyFile, "utf-8");
2927
+ body = await (0, import_promises10.readFile)(opts.bodyFile, "utf-8");
2144
2928
  }
2145
2929
  if (!body) {
2146
2930
  process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
@@ -2170,8 +2954,8 @@ var mailSendCommand = new import_commander31.Command("send").description("\uBA54
2170
2954
  });
2171
2955
 
2172
2956
  // src/commands/mail/reply.ts
2173
- var import_commander32 = require("commander");
2174
- var import_promises13 = require("fs/promises");
2957
+ var import_commander35 = require("commander");
2958
+ var import_promises11 = require("fs/promises");
2175
2959
  var import_imapflow2 = require("imapflow");
2176
2960
  async function getMessageId(config, uid) {
2177
2961
  const imap = getImapConfigOrThrow(config);
@@ -2198,12 +2982,12 @@ async function getMessageId(config, uid) {
2198
2982
  await client.logout();
2199
2983
  }
2200
2984
  }
2201
- 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) => {
2202
2986
  const globalOpts = mailReplyCommand.optsWithGlobals();
2203
2987
  const config = await getConfigOrThrow();
2204
2988
  let body = opts.body ?? "";
2205
2989
  if (opts.bodyFile) {
2206
- body = await (0, import_promises13.readFile)(opts.bodyFile, "utf-8");
2990
+ body = await (0, import_promises11.readFile)(opts.bodyFile, "utf-8");
2207
2991
  }
2208
2992
  if (!body) {
2209
2993
  process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
@@ -2239,19 +3023,19 @@ var mailReplyCommand = new import_commander32.Command("reply").description("\uBA
2239
3023
  });
2240
3024
 
2241
3025
  // src/index.ts
2242
- var program = new import_commander33.Command();
2243
- program.name("dooray").description("Dooray REST API CLI").version("0.4.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
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");
2244
3028
  program.hook("preAction", () => {
2245
3029
  const opts = program.opts();
2246
3030
  if (opts.color === false || process.env.NO_COLOR) {
2247
3031
  import_chalk7.default.level = 0;
2248
3032
  }
2249
3033
  });
2250
- 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");
2251
3035
  projectCommand.addCommand(projectListCommand);
2252
3036
  projectCommand.addCommand(projectMembersCommand);
2253
3037
  projectCommand.addCommand(projectWorkflowsCommand);
2254
- 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");
2255
3039
  postCommand.addCommand(postListCommand);
2256
3040
  postCommand.addCommand(postSearchCommand);
2257
3041
  postCommand.addCommand(postGetCommand);
@@ -2259,34 +3043,35 @@ postCommand.addCommand(postEditCommand);
2259
3043
  postCommand.addCommand(postCreateCommand);
2260
3044
  postCommand.addCommand(postDoneCommand);
2261
3045
  postCommand.addCommand(postWorkflowCommand);
2262
- 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");
2263
3047
  commentCommand.addCommand(commentListCommand);
2264
3048
  commentCommand.addCommand(commentAddCommand);
2265
3049
  commentCommand.addCommand(commentEditCommand);
2266
3050
  commentCommand.addCommand(commentDeleteCommand);
2267
3051
  postCommand.addCommand(commentCommand);
2268
- 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");
2269
3053
  fileCommand.addCommand(fileListCommand);
2270
3054
  fileCommand.addCommand(fileDownloadCommand);
2271
3055
  fileCommand.addCommand(fileDownloadAllCommand);
2272
3056
  fileCommand.addCommand(fileUploadCommand);
2273
3057
  fileCommand.addCommand(fileDeleteCommand);
2274
3058
  postCommand.addCommand(fileCommand);
2275
- 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");
2276
3060
  wikiCommand.addCommand(wikiListCommand);
2277
3061
  wikiCommand.addCommand(wikiPagesCommand);
2278
- 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");
2279
3063
  wikiPageCommand.addCommand(wikiPageGetCommand);
2280
3064
  wikiPageCommand.addCommand(wikiPageCreateCommand);
2281
3065
  wikiPageCommand.addCommand(wikiPageEditCommand);
2282
3066
  wikiCommand.addCommand(wikiPageCommand);
2283
- 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");
2284
3068
  mailCommand.addCommand(mailListCommand);
2285
3069
  mailCommand.addCommand(mailGetCommand);
2286
3070
  mailCommand.addCommand(mailSendCommand);
2287
3071
  mailCommand.addCommand(mailReplyCommand);
2288
3072
  program.addCommand(setupCommand);
2289
3073
  program.addCommand(configCommand);
3074
+ program.addCommand(memberCommand);
2290
3075
  program.addCommand(cacheCommand);
2291
3076
  program.addCommand(doctorCommand);
2292
3077
  program.addCommand(projectCommand);