@bifos/dooray-cli 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_commander40 = require("commander");
27
+ var import_commander46 = require("commander");
28
28
  var import_chalk7 = __toESM(require("chalk"));
29
29
 
30
30
  // src/commands/config.ts
@@ -127,10 +127,13 @@ async function setConfigValue(key, value) {
127
127
  case "smtp-port":
128
128
  config.smtpPort = parseInt(value, 10);
129
129
  break;
130
+ case "track-last-run":
131
+ config.trackLastRun = value === "true";
132
+ break;
130
133
  default:
131
134
  throw new DoorayCliError(
132
135
  `\uC54C \uC218 \uC5C6\uB294 \uC124\uC815 \uD0A4: ${key}
133
- \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url, tenant-name, imap-host, imap-port, imap-username, imap-password, smtp-host, smtp-port`,
136
+ \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD0A4: api-key, base-url, tenant-name, imap-host, imap-port, imap-username, imap-password, smtp-host, smtp-port, track-last-run`,
134
137
  EXIT_CONFIG_ERROR
135
138
  );
136
139
  }
@@ -525,6 +528,13 @@ var DoorayApiClient = class {
525
528
  return toDoorayCliError(e);
526
529
  }
527
530
  }
531
+ async getPostComment(projectId, postId, logId) {
532
+ try {
533
+ return await this.api.get(`project/v1/projects/${projectId}/posts/${postId}/logs/${logId}`).json();
534
+ } catch (e) {
535
+ return toDoorayCliError(e);
536
+ }
537
+ }
528
538
  async createPostComment(projectId, postId, body) {
529
539
  try {
530
540
  return await this.api.post(`project/v1/projects/${projectId}/posts/${postId}/logs`, { json: body }).json();
@@ -567,6 +577,23 @@ var DoorayApiClient = class {
567
577
  return toDoorayCliError(e);
568
578
  }
569
579
  }
580
+ async searchMembers(params) {
581
+ try {
582
+ return await this.api.get("common/v1/members", {
583
+ searchParams: {
584
+ ...params.name && { name: params.name },
585
+ ...params.externalEmailAddresses && { externalEmailAddresses: params.externalEmailAddresses },
586
+ ...params.userCode && { userCode: params.userCode },
587
+ ...params.userCodeExact && { userCodeExact: params.userCodeExact },
588
+ ...params.idProviderUserId && { idProviderUserId: params.idProviderUserId },
589
+ ...params.page != null && { page: params.page },
590
+ ...params.size != null && { size: params.size }
591
+ }
592
+ }).json();
593
+ } catch (e) {
594
+ return toDoorayCliError(e);
595
+ }
596
+ }
570
597
  // ─── Workflows ──────────────────────────────────────
571
598
  async getProjectWorkflows(projectId) {
572
599
  try {
@@ -780,13 +807,24 @@ var WIKIS_TTL_MS = 864e5;
780
807
  // src/resolvers/me.ts
781
808
  async function ensureMe(client) {
782
809
  const entry = await getMe();
783
- if (entry && !isExpired(entry.updatedAt, ME_TTL_MS)) {
810
+ if (entry && !isExpired(entry.updatedAt, ME_TTL_MS) && entry.data.orgId) {
784
811
  return entry.data;
785
812
  }
786
813
  const res = await client.getMe();
787
- const me = { id: res.result.id, name: res.result.name };
788
- await setMe(me);
789
- return me;
814
+ const orgId = res.result.defaultOrganization?.id ?? "";
815
+ if (!orgId) {
816
+ throw new DoorayCliError(
817
+ "orgId\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (getMe \uC751\uB2F5\uC5D0 defaultOrganization.id \uB204\uB77D). dooray cache clear \uD6C4 \uC7AC\uC2DC\uB3C4\uD558\uC138\uC694.",
818
+ EXIT_PARAM_ERROR
819
+ );
820
+ }
821
+ const cached = {
822
+ id: res.result.id,
823
+ name: res.result.name,
824
+ orgId
825
+ };
826
+ await setMe(cached);
827
+ return cached;
790
828
  }
791
829
 
792
830
  // src/commands/doctor.ts
@@ -973,6 +1011,11 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
973
1011
  }
974
1012
  }
975
1013
  }
1014
+ const trackLastRun = await confirm2({
1015
+ message: "feedback --last \uBAA8\uB4DC \uD65C\uC131\uD654 (\uC9C1\uC804 \uBA85\uB839 \uC5D0\uB7EC\uB97C GitHub issue\uC5D0 \uC790\uB3D9 \uCCA8\uBD80)?",
1016
+ default: false,
1017
+ theme: { prefix: "\u{1F4CB}" }
1018
+ });
976
1019
  const config = {
977
1020
  version: 1,
978
1021
  apiKey,
@@ -985,6 +1028,7 @@ var setupCommand = new import_commander4.Command("setup").description("\uB300\uD
985
1028
  smtpHost: useMail ? DEFAULTS.smtpHost : existing?.smtpHost,
986
1029
  smtpPort: useMail ? DEFAULTS.smtpPort : existing?.smtpPort
987
1030
  };
1031
+ config.trackLastRun = trackLastRun;
988
1032
  await saveConfig(config);
989
1033
  console.log(
990
1034
  import_chalk4.default.green(
@@ -1084,17 +1128,29 @@ function output(opts, data) {
1084
1128
  // src/utils/spinner.ts
1085
1129
  var import_ora = __toESM(require("ora"));
1086
1130
  var current = null;
1131
+ var quiet = false;
1132
+ var noopSpinner = new Proxy({}, {
1133
+ get(_target, prop) {
1134
+ if (prop === "text" || prop === "prefixText" || prop === "suffixText") return "";
1135
+ if (prop === "isSpinning") return false;
1136
+ return () => noopSpinner;
1137
+ },
1138
+ set() {
1139
+ return true;
1140
+ }
1141
+ });
1142
+ function setQuiet(value) {
1143
+ quiet = value;
1144
+ }
1087
1145
  function startSpinner(text) {
1146
+ if (quiet) return noopSpinner;
1088
1147
  current = (0, import_ora.default)({ text, stream: process.stderr }).start();
1089
1148
  return current;
1090
1149
  }
1091
1150
  function stopSpinner(success, text) {
1092
1151
  if (!current) return;
1093
- if (success === false) {
1094
- current.fail(text);
1095
- } else {
1096
- current.succeed(text);
1097
- }
1152
+ if (success === false) current.fail(text);
1153
+ else current.succeed(text);
1098
1154
  current = null;
1099
1155
  }
1100
1156
 
@@ -1312,6 +1368,12 @@ async function ensureMemberGroups(client, projectId) {
1312
1368
  await setMemberGroups(projectId, items);
1313
1369
  return items;
1314
1370
  }
1371
+ async function resolveMemberGroup(client, projectId, input2) {
1372
+ const groups = await ensureMemberGroups(client, projectId);
1373
+ const adapter = groups.map((g) => ({ name: g.code, id: g.id, code: g.code }));
1374
+ const match = matchByName(adapter, input2, "\uADF8\uB8F9", (g) => `${g.code} (${g.id})`);
1375
+ return { id: match.id, code: match.code };
1376
+ }
1315
1377
 
1316
1378
  // src/commands/project/groups.ts
1317
1379
  var projectGroupsCommand = new import_commander8.Command("groups").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uADF8\uB8F9 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
@@ -1364,6 +1426,35 @@ async function ensureTags(client, projectId) {
1364
1426
  await setTags(projectId, items);
1365
1427
  return items;
1366
1428
  }
1429
+ function buildMandatoryHint(allTags, missingGroupIds) {
1430
+ const lines = [];
1431
+ for (const groupId of missingGroupIds) {
1432
+ const groupTags = allTags.filter((t) => t.groupId === groupId);
1433
+ const groupName = groupTags[0]?.groupName ?? groupId;
1434
+ const candidates = groupTags.map((t) => t.name).join(", ");
1435
+ lines.push(` - "${groupName}": ${candidates || "(\uD0DC\uADF8 \uC5C6\uC74C)"}`);
1436
+ }
1437
+ return lines.join("\n");
1438
+ }
1439
+ async function validateMandatoryTags(client, projectId) {
1440
+ const allTags = await ensureTags(client, projectId);
1441
+ const missingGroupIds = [];
1442
+ const seen = /* @__PURE__ */ new Set();
1443
+ for (const t of allTags) {
1444
+ if (t.groupMandatory && t.groupId && !seen.has(t.groupId)) {
1445
+ seen.add(t.groupId);
1446
+ missingGroupIds.push(t.groupId);
1447
+ }
1448
+ }
1449
+ if (missingGroupIds.length === 0) return;
1450
+ throw new DoorayCliError(
1451
+ `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1452
+ ` + buildMandatoryHint(allTags, missingGroupIds) + `
1453
+
1454
+ \uB2E4\uC2DC \uC2DC\uB3C4: --tag "<\uADF8\uB8F9>: <\uD6C4\uBCF4>" \uD615\uC2DD\uC73C\uB85C \uCD94\uAC00`,
1455
+ EXIT_PARAM_ERROR
1456
+ );
1457
+ }
1367
1458
  async function resolveTags(client, projectId, inputs) {
1368
1459
  const allTags = await ensureTags(client, projectId);
1369
1460
  const selected = inputs.map(
@@ -1379,14 +1470,14 @@ async function resolveTags(client, projectId, inputs) {
1379
1470
  if (t.groupMandatory && t.groupId) mandatoryGroups.set(t.groupId, t.groupName ?? t.groupId);
1380
1471
  }
1381
1472
  const coveredGroups = new Set(selected.map((t) => t.groupId).filter((g) => !!g));
1382
- const missing = [];
1383
- for (const [gid, gname] of mandatoryGroups) {
1384
- if (!coveredGroups.has(gid)) missing.push(gname);
1473
+ const missingIds = [];
1474
+ for (const [gid] of mandatoryGroups) {
1475
+ if (!coveredGroups.has(gid)) missingIds.push(gid);
1385
1476
  }
1386
- if (missing.length > 0) {
1477
+ if (missingIds.length > 0) {
1387
1478
  throw new DoorayCliError(
1388
1479
  `\uD544\uC218 \uD0DC\uADF8 \uADF8\uB8F9\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (\uADF8\uB8F9\uB2F9 1\uAC1C \uC774\uC0C1 \uD544\uC694):
1389
- ` + missing.map((g) => ` - ${g}`).join("\n"),
1480
+ ` + buildMandatoryHint(allTags, missingIds),
1390
1481
  EXIT_PARAM_ERROR
1391
1482
  );
1392
1483
  }
@@ -1553,9 +1644,13 @@ async function resolvePost(client, projectId, postNumber) {
1553
1644
 
1554
1645
  // src/utils/dooray-url.ts
1555
1646
  var TASK_URL_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/to\/(\d+)(?:[/?#].*)?$/;
1647
+ var TASK_URL_ALT_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/(?:\d+)\/(\d+)(?:[/?#].*)?$/;
1556
1648
  function parseDoorayTaskUrl(input2) {
1557
- const m = TASK_URL_RE.exec(input2);
1558
- return m ? m[1] : null;
1649
+ const m1 = TASK_URL_RE.exec(input2);
1650
+ if (m1) return m1[1];
1651
+ const m2 = TASK_URL_ALT_RE.exec(input2);
1652
+ if (m2) return m2[1];
1653
+ return null;
1559
1654
  }
1560
1655
  function isLikelyDoorayUrl(input2) {
1561
1656
  return /^https?:\/\//.test(input2);
@@ -1968,7 +2063,7 @@ var postCreateCommand = new import_commander14.Command("create").description("\u
1968
2063
  const ccUsers = opts.cc ? await resolveUsers2(client, projectId, opts.cc) : [];
1969
2064
  const tagInputs = (opts.tag ?? []).filter((s) => s.length > 0);
1970
2065
  const [tagIds, parentPostId, milestoneId] = await Promise.all([
1971
- tagInputs.length > 0 ? resolveTags(client, projectId, tagInputs) : Promise.resolve(void 0),
2066
+ tagInputs.length > 0 ? resolveTags(client, projectId, tagInputs) : validateMandatoryTags(client, projectId).then(() => void 0),
1972
2067
  opts.parent ? resolvePostRef(client, opts.parent) : Promise.resolve(void 0),
1973
2068
  opts.milestone ? resolveMilestone(client, projectId, opts.milestone) : Promise.resolve(void 0)
1974
2069
  ]);
@@ -2241,7 +2336,35 @@ var commentLatestCommand = new import_commander18.Command("latest").description(
2241
2336
 
2242
2337
  // src/commands/post/comment/add.ts
2243
2338
  var import_commander19 = require("commander");
2244
- var commentAddCommand = new import_commander19.Command("add").description("\uB313\uAE00 \uCD94\uAC00").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, postNumberStr, opts) => {
2339
+
2340
+ // src/utils/mention.ts
2341
+ function buildMemberMention(m, me) {
2342
+ const title = m.memberId === me.id ? "me" : "member";
2343
+ return `[@${m.name}](dooray://${me.orgId}/members/${m.memberId} "${title}")`;
2344
+ }
2345
+ function buildGroupMention(g, me) {
2346
+ return `[@${g.projectCode}/${g.code}](dooray://${me.orgId}/member-groups/${g.groupId})`;
2347
+ }
2348
+ function prependMentions(body, members, groups, me) {
2349
+ const parts = [];
2350
+ for (const m of members) parts.push(buildMemberMention(m, me));
2351
+ for (const g of groups) parts.push(buildGroupMention(g, me));
2352
+ if (parts.length === 0) return body;
2353
+ return parts.join(" ") + " " + body;
2354
+ }
2355
+
2356
+ // src/commands/post/comment/add.ts
2357
+ var commentAddCommand = new import_commander19.Command("add").description("\uB313\uAE00 \uCD94\uAC00").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option(
2358
+ "--mention <name>",
2359
+ "\uBA64\uBC84 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, \uC774\uB984 \uBD80\uBD84\uC77C\uCE58)",
2360
+ (value, prev) => [...prev, value],
2361
+ []
2362
+ ).option(
2363
+ "--mention-group <code>",
2364
+ "\uADF8\uB8F9 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, code \uBD80\uBD84\uC77C\uCE58)",
2365
+ (value, prev) => [...prev, value],
2366
+ []
2367
+ ).action(async (project, postNumberStr, opts) => {
2245
2368
  const globalOpts = commentAddCommand.optsWithGlobals();
2246
2369
  const config = await getConfigOrThrow();
2247
2370
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2254,12 +2377,32 @@ var commentAddCommand = new import_commander19.Command("add").description("\uB31
2254
2377
  }
2255
2378
  }
2256
2379
  startSpinner("\uB313\uAE00 \uCD94\uAC00 \uC911...");
2257
- const { projectId, postId } = await resolvePostInput(client, {
2380
+ const { projectId, postId, projectCode } = await resolvePostInput(client, {
2258
2381
  projectArg: project,
2259
2382
  postNumberArg: postNumberStr,
2260
2383
  idOpt: opts.id,
2261
2384
  urlOpt: opts.url
2262
2385
  });
2386
+ const mentionInputs = (opts.mention ?? []).filter((s) => s.length > 0);
2387
+ const groupInputs = (opts.mentionGroup ?? []).filter((s) => s.length > 0);
2388
+ if (mentionInputs.length > 0 || groupInputs.length > 0) {
2389
+ const me = await ensureMe(client);
2390
+ const memberIds = await Promise.all(
2391
+ mentionInputs.map((name) => resolveMember(client, projectId, name))
2392
+ );
2393
+ const nameMap = await buildMemberNameMap(client, projectId);
2394
+ const members = memberIds.map((memberId) => ({
2395
+ memberId,
2396
+ name: nameMap.get(memberId) ?? memberId
2397
+ }));
2398
+ const groups = await Promise.all(
2399
+ groupInputs.map(async (code) => {
2400
+ const g = await resolveMemberGroup(client, projectId, code);
2401
+ return { groupId: g.id, code: g.code, projectCode };
2402
+ })
2403
+ );
2404
+ bodyContent = prependMentions(bodyContent, members, groups, me);
2405
+ }
2263
2406
  const res = await client.createPostComment(projectId, postId, {
2264
2407
  body: { mimeType: "text/x-markdown", content: bodyContent }
2265
2408
  });
@@ -2276,7 +2419,17 @@ var commentAddCommand = new import_commander19.Command("add").description("\uB31
2276
2419
 
2277
2420
  // src/commands/post/comment/edit.ts
2278
2421
  var import_commander20 = require("commander");
2279
- var commentEditCommand = new import_commander20.Command("edit").description("\uB313\uAE00 \uC218\uC815 ($EDITOR \uB610\uB294 --body \uC635\uC158)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").action(async (arg1, arg2, arg3, opts) => {
2422
+ var commentEditCommand = new import_commander20.Command("edit").description("\uB313\uAE00 \uC218\uC815 ($EDITOR \uB610\uB294 --body \uC635\uC158)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC / Dooray URL / \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 \uB313\uAE00 ID (\uBAA8\uB4DC\uBCC4)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <commentId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--body <text>", "\uB313\uAE00 \uBCF8\uBB38 \uBCC0\uACBD (- \uC785\uB825 \uC2DC stdin, non-interactive)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin, non-interactive)").option(
2423
+ "--mention <name>",
2424
+ "\uBA64\uBC84 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, \uC774\uB984 \uBD80\uBD84\uC77C\uCE58)",
2425
+ (value, prev) => [...prev, value],
2426
+ []
2427
+ ).option(
2428
+ "--mention-group <code>",
2429
+ "\uADF8\uB8F9 \uBA58\uC158 (\uBC18\uBCF5 \uAC00\uB2A5, code \uBD80\uBD84\uC77C\uCE58)",
2430
+ (value, prev) => [...prev, value],
2431
+ []
2432
+ ).action(async (arg1, arg2, arg3, opts) => {
2280
2433
  const config = await getConfigOrThrow();
2281
2434
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2282
2435
  let projectArg;
@@ -2319,7 +2472,7 @@ var commentEditCommand = new import_commander20.Command("edit").description("\uB
2319
2472
  );
2320
2473
  }
2321
2474
  startSpinner("\uB313\uAE00 \uC870\uD68C \uC911...");
2322
- const { projectId, postId } = await resolvePostInput(client, {
2475
+ const { projectId, postId, projectCode } = await resolvePostInput(client, {
2323
2476
  projectArg,
2324
2477
  postNumberArg,
2325
2478
  idOpt: opts.id,
@@ -2333,14 +2486,38 @@ var commentEditCommand = new import_commander20.Command("edit").description("\uB
2333
2486
  `);
2334
2487
  process.exit(1);
2335
2488
  }
2489
+ const mentionInputs = (opts.mention ?? []).filter((s) => s.length > 0);
2490
+ const groupInputs = (opts.mentionGroup ?? []).filter((s) => s.length > 0);
2491
+ let mentionPrefix = "";
2492
+ if (mentionInputs.length > 0 || groupInputs.length > 0) {
2493
+ const me = await ensureMe(client);
2494
+ const memberIds = await Promise.all(
2495
+ mentionInputs.map((name) => resolveMember(client, projectId, name))
2496
+ );
2497
+ const nameMap = await buildMemberNameMap(client, projectId);
2498
+ const members = memberIds.map((memberId) => ({
2499
+ memberId,
2500
+ name: nameMap.get(memberId) ?? memberId
2501
+ }));
2502
+ const groups = await Promise.all(
2503
+ groupInputs.map(async (code) => {
2504
+ const g = await resolveMemberGroup(client, projectId, code);
2505
+ return { groupId: g.id, code: g.code, projectCode };
2506
+ })
2507
+ );
2508
+ mentionPrefix = prependMentions("", members, groups, me).trimEnd();
2509
+ }
2336
2510
  let edited = await readBodyInputOrNull(opts);
2337
2511
  if (edited == null) {
2338
- const original = comment.body.content;
2339
- edited = await openInEditor(original);
2340
- if (original === edited) {
2512
+ const rawContent = comment.body.content;
2513
+ const editorSeed = mentionPrefix ? mentionPrefix + " " + rawContent : rawContent;
2514
+ edited = await openInEditor(editorSeed);
2515
+ if (rawContent === edited) {
2341
2516
  process.stdout.write("\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C\n");
2342
2517
  return;
2343
2518
  }
2519
+ } else if (mentionPrefix) {
2520
+ edited = mentionPrefix + " " + edited;
2344
2521
  }
2345
2522
  startSpinner("\uB313\uAE00 \uC218\uC815 \uC911...");
2346
2523
  await client.updatePostComment(projectId, postId, commentId, {
@@ -2408,14 +2585,348 @@ var commentDeleteCommand = new import_commander21.Command("delete").description(
2408
2585
  `);
2409
2586
  });
2410
2587
 
2411
- // src/commands/post/file/list.ts
2588
+ // src/commands/post/comment/file/index.ts
2589
+ var import_commander26 = require("commander");
2590
+
2591
+ // src/commands/post/comment/file/list.ts
2412
2592
  var import_commander22 = require("commander");
2413
2593
  function formatSize(bytes) {
2414
2594
  if (bytes < 1024) return `${bytes}B`;
2415
2595
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
2416
2596
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
2417
2597
  }
2418
- var fileListCommand = new import_commander22.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
2598
+ var listCommentFileCommand = new import_commander22.Command("list").description("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 (positional \uBAA8\uB4DC)").argument("[arg3]", "\uB313\uAE00 ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2599
+ const globalOpts = listCommentFileCommand.optsWithGlobals();
2600
+ const config = await getConfigOrThrow();
2601
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2602
+ let projectArg;
2603
+ let postNumberArg;
2604
+ let logId = opts.commentId;
2605
+ if (opts.id || opts.url) {
2606
+ if (arg2 || arg3) {
2607
+ throw new DoorayCliError(
2608
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. --comment-id \uC635\uC158 \uC0AC\uC6A9\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
2609
+ EXIT_PARAM_ERROR
2610
+ );
2611
+ }
2612
+ logId = logId ?? arg1;
2613
+ } else if (arg3) {
2614
+ projectArg = arg1;
2615
+ postNumberArg = arg2;
2616
+ logId = logId ?? arg3;
2617
+ } else if (arg1 && !arg2) {
2618
+ projectArg = arg1;
2619
+ if (!logId) {
2620
+ throw new DoorayCliError(
2621
+ "URL/--id \uBAA8\uB4DC\uC5D0\uC11C\uB294 --comment-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2622
+ EXIT_PARAM_ERROR
2623
+ );
2624
+ }
2625
+ } else {
2626
+ projectArg = arg1;
2627
+ postNumberArg = arg2;
2628
+ if (!logId) {
2629
+ throw new DoorayCliError(
2630
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2631
+ EXIT_PARAM_ERROR
2632
+ );
2633
+ }
2634
+ }
2635
+ if (!logId) {
2636
+ throw new DoorayCliError("<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", EXIT_PARAM_ERROR);
2637
+ }
2638
+ startSpinner("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
2639
+ const { projectId, postId } = await resolvePostInput(client, {
2640
+ projectArg,
2641
+ postNumberArg,
2642
+ idOpt: opts.id,
2643
+ urlOpt: opts.url
2644
+ });
2645
+ const res = await client.getPostComment(projectId, postId, logId);
2646
+ const files = res.result.files ?? [];
2647
+ stopSpinner(true, `\uCCA8\uBD80 \uD30C\uC77C ${files.length}\uAC1C`);
2648
+ if (files.length === 0) {
2649
+ if (globalOpts.json) {
2650
+ process.stdout.write("[]\n");
2651
+ } else if (!globalOpts.quiet) {
2652
+ process.stdout.write("\uCCA8\uBD80 \uC5C6\uC74C\n");
2653
+ }
2654
+ return;
2655
+ }
2656
+ output(globalOpts, {
2657
+ headers: ["\uD30C\uC77C\uBA85", "\uD06C\uAE30", "ID"],
2658
+ rows: files.map((f) => [f.name, formatSize(f.size), f.id]),
2659
+ raw: files,
2660
+ ids: files.map((f) => f.id)
2661
+ });
2662
+ });
2663
+
2664
+ // src/commands/post/comment/file/upload.ts
2665
+ var import_commander23 = require("commander");
2666
+ var import_node_path4 = require("path");
2667
+
2668
+ // src/utils/comment-files.ts
2669
+ function appendFileReference(body, fileName, fileId) {
2670
+ const safeName = fileName.replace(/[\[\]]/g, "");
2671
+ const ref = `![${safeName}](/files/${fileId})`;
2672
+ if (body.length === 0) return ref;
2673
+ const trailing = body.endsWith("\n") ? "" : "\n";
2674
+ return `${body}${trailing}
2675
+ ${ref}`;
2676
+ }
2677
+ function removeFileReference(body, fileId) {
2678
+ const escaped = fileId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2679
+ const refSource = `!\\[[^\\]]*\\]\\(/files/${escaped}\\)`;
2680
+ const lineRe = new RegExp(`^[ \\t]*${refSource}[ \\t]*$\\n?`, "gm");
2681
+ const inlineRe = new RegExp(refSource, "g");
2682
+ return body.replace(lineRe, "").replace(inlineRe, "");
2683
+ }
2684
+
2685
+ // src/commands/post/comment/file/upload.ts
2686
+ var uploadCommentFileCommand = new import_commander23.Command("upload").description("\uB313\uAE00\uC5D0 \uD30C\uC77C \uC5C5\uB85C\uB4DC (post-level \uD30C\uC77C \uC5C5\uB85C\uB4DC + \uB313\uAE00 reference \uCD94\uAC00, ADR-024)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uB313\uAE00 ID (positional \uBAA8\uB4DC)").argument("[arg4]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, arg4, opts) => {
2687
+ const globalOpts = uploadCommentFileCommand.optsWithGlobals();
2688
+ const config = await getConfigOrThrow();
2689
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2690
+ let projectArg;
2691
+ let postNumberArg;
2692
+ let logId = opts.commentId;
2693
+ let filePath = opts.file;
2694
+ if (opts.id || opts.url) {
2695
+ if (arg3 || arg4) {
2696
+ throw new DoorayCliError(
2697
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC640 \uD30C\uC77C \uACBD\uB85C \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2698
+ EXIT_PARAM_ERROR
2699
+ );
2700
+ }
2701
+ logId = logId ?? arg1;
2702
+ filePath = filePath ?? arg2;
2703
+ } else if (arg4) {
2704
+ projectArg = arg1;
2705
+ postNumberArg = arg2;
2706
+ logId = logId ?? arg3;
2707
+ filePath = filePath ?? arg4;
2708
+ } else if (arg3) {
2709
+ projectArg = arg1;
2710
+ postNumberArg = arg2;
2711
+ logId = logId ?? arg3;
2712
+ } else if (arg1 && !arg2) {
2713
+ projectArg = arg1;
2714
+ } else {
2715
+ projectArg = arg1;
2716
+ postNumberArg = arg2;
2717
+ }
2718
+ if (!logId) {
2719
+ throw new DoorayCliError(
2720
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2721
+ EXIT_PARAM_ERROR
2722
+ );
2723
+ }
2724
+ if (!filePath) {
2725
+ throw new DoorayCliError(
2726
+ "<path>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 4\uBC88\uC9F8 \uB610\uB294 --file \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2727
+ EXIT_PARAM_ERROR
2728
+ );
2729
+ }
2730
+ const { projectId, postId } = await resolvePostInput(client, {
2731
+ projectArg,
2732
+ postNumberArg,
2733
+ idOpt: opts.id,
2734
+ urlOpt: opts.url
2735
+ });
2736
+ startSpinner("\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC911...");
2737
+ const uploadRes = await client.uploadPostFile(projectId, postId, filePath);
2738
+ const fileId = uploadRes.result.id;
2739
+ const fileName = (0, import_node_path4.basename)(filePath);
2740
+ stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
2741
+ startSpinner("\uB313\uAE00 reference \uCD94\uAC00 \uC911...");
2742
+ try {
2743
+ const commentRes = await client.getPostComment(projectId, postId, logId);
2744
+ const currentBody = commentRes.result.body.content;
2745
+ const newBody = appendFileReference(currentBody, fileName, fileId);
2746
+ await client.updatePostComment(projectId, postId, logId, {
2747
+ body: { mimeType: "text/x-markdown", content: newBody }
2748
+ });
2749
+ stopSpinner(true, "reference \uCD94\uAC00 \uC644\uB8CC");
2750
+ } catch {
2751
+ stopSpinner(false, "");
2752
+ throw new DoorayCliError(
2753
+ `\uC5C5\uB85C\uB4DC OK / \uB313\uAE00 reference \uCD94\uAC00 \uC2E4\uD328. fileId=${fileId} \u2014 'dooray post comment file delete' \uB610\uB294 \uC218\uB3D9 PUT \uC73C\uB85C \uC815\uB9AC\uD558\uC138\uC694.`,
2754
+ EXIT_API_ERROR
2755
+ );
2756
+ }
2757
+ if (globalOpts.json) {
2758
+ printJson({ fileId, fileName, commentId: logId });
2759
+ } else if (globalOpts.quiet) {
2760
+ process.stdout.write(`${fileId}
2761
+ `);
2762
+ } else {
2763
+ process.stdout.write(`\uC5C5\uB85C\uB4DC + \uB313\uAE00 reference \uCD94\uAC00: fileId=${fileId}
2764
+ `);
2765
+ }
2766
+ });
2767
+
2768
+ // src/commands/post/comment/file/download.ts
2769
+ var import_commander24 = require("commander");
2770
+ var import_promises8 = require("fs/promises");
2771
+ var import_node_path5 = require("path");
2772
+ var downloadCommentFileCommand = new import_commander24.Command("download").description(
2773
+ "\uB313\uAE00\uC5D0 \uCCA8\uBD80\uB41C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC (\uD604\uC7AC comment-id \uB294 \uBBF8\uC0AC\uC6A9 \u2014 \uBA58\uD0C8 \uBAA8\uB378 \uC77C\uAD00\uC131, \uBBF8\uB798 \uB313\uAE00 \uB2E8\uC704 \uAD8C\uD55C \uD638\uD658\uC6A9)"
2774
+ ).argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uB313\uAE00 ID (positional \uBAA8\uB4DC)").argument("[arg4]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("--out <path>", "\uC800\uC7A5 \uACBD\uB85C (\uBBF8\uC9C0\uC815 \uC2DC \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC6D0\uBCF8 \uD30C\uC77C\uBA85\uC73C\uB85C \uC800\uC7A5)", ".").action(async (arg1, arg2, arg3, arg4, opts) => {
2775
+ const config = await getConfigOrThrow();
2776
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2777
+ let projectArg;
2778
+ let postNumberArg;
2779
+ let _commentId = opts.commentId;
2780
+ let fileId = opts.fileId;
2781
+ if (opts.id || opts.url) {
2782
+ if (arg3 || arg4) {
2783
+ throw new DoorayCliError(
2784
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC640 \uD30C\uC77C ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2785
+ EXIT_PARAM_ERROR
2786
+ );
2787
+ }
2788
+ _commentId = _commentId ?? arg1;
2789
+ fileId = fileId ?? arg2;
2790
+ } else if (arg4) {
2791
+ projectArg = arg1;
2792
+ postNumberArg = arg2;
2793
+ _commentId = _commentId ?? arg3;
2794
+ fileId = fileId ?? arg4;
2795
+ } else if (arg3) {
2796
+ projectArg = arg1;
2797
+ postNumberArg = arg2;
2798
+ _commentId = _commentId ?? arg3;
2799
+ } else if (arg1 && !arg2) {
2800
+ projectArg = arg1;
2801
+ } else {
2802
+ projectArg = arg1;
2803
+ postNumberArg = arg2;
2804
+ }
2805
+ if (!fileId) {
2806
+ throw new DoorayCliError(
2807
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 4\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2808
+ EXIT_PARAM_ERROR
2809
+ );
2810
+ }
2811
+ startSpinner("\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC911...");
2812
+ const { projectId, postId } = await resolvePostInput(client, {
2813
+ projectArg,
2814
+ postNumberArg,
2815
+ idOpt: opts.id,
2816
+ urlOpt: opts.url
2817
+ });
2818
+ const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
2819
+ const safeName = (0, import_node_path5.basename)(fileName);
2820
+ const outputPath = (0, import_node_path5.join)(opts.out, safeName);
2821
+ await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
2822
+ stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
2823
+ process.stdout.write(`${outputPath}
2824
+ `);
2825
+ });
2826
+
2827
+ // src/commands/post/comment/file/delete.ts
2828
+ var import_commander25 = require("commander");
2829
+ var deleteCommentFileCommand = new import_commander25.Command("delete").description("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uC0AD\uC81C (\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 + \uD30C\uC77C \uC0AD\uC81C, ADR-024)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uB313\uAE00 ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uB313\uAE00 ID (positional \uBAA8\uB4DC)").argument("[arg4]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--comment-id <logId>", "\uB313\uAE00 ID (positional \uB300\uCCB4)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("--yes", "\uD655\uC778 \uC5C6\uC774 \uC0AD\uC81C").action(async (arg1, arg2, arg3, arg4, opts) => {
2830
+ const config = await getConfigOrThrow();
2831
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2832
+ let projectArg;
2833
+ let postNumberArg;
2834
+ let logId = opts.commentId;
2835
+ let fileId = opts.fileId;
2836
+ if (opts.id || opts.url) {
2837
+ if (arg3 || arg4) {
2838
+ throw new DoorayCliError(
2839
+ "--id/--url \uBAA8\uB4DC\uC5D0\uC11C\uB294 \uB313\uAE00 ID \uC640 \uD30C\uC77C ID \uC678 \uCD94\uAC00 positional \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2840
+ EXIT_PARAM_ERROR
2841
+ );
2842
+ }
2843
+ logId = logId ?? arg1;
2844
+ fileId = fileId ?? arg2;
2845
+ } else if (arg4) {
2846
+ projectArg = arg1;
2847
+ postNumberArg = arg2;
2848
+ logId = logId ?? arg3;
2849
+ fileId = fileId ?? arg4;
2850
+ } else if (arg3) {
2851
+ projectArg = arg1;
2852
+ postNumberArg = arg2;
2853
+ logId = logId ?? arg3;
2854
+ } else if (arg1 && !arg2) {
2855
+ projectArg = arg1;
2856
+ } else {
2857
+ projectArg = arg1;
2858
+ postNumberArg = arg2;
2859
+ }
2860
+ if (!logId) {
2861
+ throw new DoorayCliError(
2862
+ "<comment-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 3\uBC88\uC9F8 \uB610\uB294 --comment-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2863
+ EXIT_PARAM_ERROR
2864
+ );
2865
+ }
2866
+ if (!fileId) {
2867
+ throw new DoorayCliError(
2868
+ "<file-id>\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. positional 4\uBC88\uC9F8 \uB610\uB294 --file-id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.",
2869
+ EXIT_PARAM_ERROR
2870
+ );
2871
+ }
2872
+ if (!opts.yes) {
2873
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
2874
+ const ok = await confirm2({
2875
+ message: `\uB313\uAE00 \uBCF8\uBB38\uC5D0\uC11C reference \uB97C \uC81C\uAC70\uD558\uACE0 \uD30C\uC77C(${fileId})\uC744 \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uAC19\uC740 post \uC758 \uB2E4\uB978 \uB313\uAE00\uC5D0\uC11C \uAC19\uC740 \uD30C\uC77C ID \uB97C \uCC38\uC870\uD558\uB294 \uACBD\uC6B0 broken \uB429\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?`
2876
+ });
2877
+ if (!ok) {
2878
+ process.stdout.write("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n");
2879
+ return;
2880
+ }
2881
+ }
2882
+ const { projectId, postId } = await resolvePostInput(client, {
2883
+ projectArg,
2884
+ postNumberArg,
2885
+ idOpt: opts.id,
2886
+ urlOpt: opts.url
2887
+ });
2888
+ startSpinner("\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 \uC911...");
2889
+ try {
2890
+ const commentRes = await client.getPostComment(projectId, postId, logId);
2891
+ const currentBody = commentRes.result.body.content;
2892
+ const newBody = removeFileReference(currentBody, fileId);
2893
+ await client.updatePostComment(projectId, postId, logId, {
2894
+ body: { mimeType: "text/x-markdown", content: newBody }
2895
+ });
2896
+ stopSpinner(true, "reference \uC81C\uAC70 \uC644\uB8CC");
2897
+ } catch {
2898
+ stopSpinner(false, "");
2899
+ throw new DoorayCliError(
2900
+ `\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 \uC2E4\uD328. \uD30C\uC77C \uC0AD\uC81C\uB97C \uC9C4\uD589\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. fileId=${fileId}`,
2901
+ EXIT_API_ERROR
2902
+ );
2903
+ }
2904
+ startSpinner("\uD30C\uC77C \uC0AD\uC81C \uC911...");
2905
+ try {
2906
+ await client.deletePostFile(projectId, postId, fileId);
2907
+ stopSpinner(true, "\uD30C\uC77C \uC0AD\uC81C \uC644\uB8CC");
2908
+ } catch {
2909
+ stopSpinner(false, "");
2910
+ throw new DoorayCliError(
2911
+ `\uB313\uAE00 \uBCF8\uBB38 reference \uC81C\uAC70 OK / \uD30C\uC77C \uC0AD\uC81C \uC2E4\uD328. 'dooray post file delete' \uB85C \uD6C4\uCC98\uB9AC\uD558\uC138\uC694. fileId=${fileId}`,
2912
+ EXIT_API_ERROR
2913
+ );
2914
+ }
2915
+ process.stdout.write(`\uD30C\uC77C(${fileId})\uC774 \uB313\uAE00(${logId})\uC5D0\uC11C \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
2916
+ `);
2917
+ });
2918
+
2919
+ // src/commands/post/comment/file/index.ts
2920
+ var commentFileCommand = new import_commander26.Command("file").description("\uB313\uAE00 \uCCA8\uBD80 \uD30C\uC77C \uAD00\uB9AC (post-level files API + \uB313\uAE00 PUT \uD569\uC131, ADR-024)").addCommand(listCommentFileCommand).addCommand(uploadCommentFileCommand).addCommand(downloadCommentFileCommand).addCommand(deleteCommentFileCommand);
2921
+
2922
+ // src/commands/post/file/list.ts
2923
+ var import_commander27 = require("commander");
2924
+ function formatSize2(bytes) {
2925
+ if (bytes < 1024) return `${bytes}B`;
2926
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
2927
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
2928
+ }
2929
+ var fileListCommand = new import_commander27.Command("list").description("\uC5C5\uBB34 \uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").action(async (project, postNumberStr, opts) => {
2419
2930
  const globalOpts = fileListCommand.optsWithGlobals();
2420
2931
  const config = await getConfigOrThrow();
2421
2932
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2433,7 +2944,7 @@ var fileListCommand = new import_commander22.Command("list").description("\uC5C5
2433
2944
  rows: res.result.map((f) => [
2434
2945
  f.id,
2435
2946
  f.name,
2436
- formatSize(f.size),
2947
+ formatSize2(f.size),
2437
2948
  f.mimeType,
2438
2949
  f.createdAt
2439
2950
  ]),
@@ -2443,10 +2954,10 @@ var fileListCommand = new import_commander22.Command("list").description("\uC5C5
2443
2954
  });
2444
2955
 
2445
2956
  // src/commands/post/file/download.ts
2446
- var import_commander23 = require("commander");
2447
- var import_promises8 = require("fs/promises");
2448
- var import_node_path4 = require("path");
2449
- var fileDownloadCommand = new import_commander23.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (arg1, arg2, arg3, opts) => {
2957
+ var import_commander28 = require("commander");
2958
+ var import_promises9 = require("fs/promises");
2959
+ var import_node_path6 = require("path");
2960
+ var fileDownloadCommand = new import_commander28.Command("download").description("\uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (arg1, arg2, arg3, opts) => {
2450
2961
  const config = await getConfigOrThrow();
2451
2962
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2452
2963
  let projectArg;
@@ -2493,18 +3004,18 @@ var fileDownloadCommand = new import_commander23.Command("download").description
2493
3004
  urlOpt: opts.url
2494
3005
  });
2495
3006
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, fileId);
2496
- const outputPath = (0, import_node_path4.join)(opts.output, fileName);
2497
- await (0, import_promises8.writeFile)(outputPath, Buffer.from(buffer));
3007
+ const outputPath = (0, import_node_path6.join)(opts.output, fileName);
3008
+ await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
2498
3009
  stopSpinner(true, "\uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC");
2499
3010
  process.stdout.write(`${outputPath}
2500
3011
  `);
2501
3012
  });
2502
3013
 
2503
3014
  // src/commands/post/file/download-all.ts
2504
- var import_commander24 = require("commander");
2505
- var import_promises9 = require("fs/promises");
2506
- var import_node_path5 = require("path");
2507
- var fileDownloadAllCommand = new import_commander24.Command("download-all").description("\uC5C5\uBB34\uC758 \uBAA8\uB4E0 \uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
3015
+ var import_commander29 = require("commander");
3016
+ var import_promises10 = require("fs/promises");
3017
+ var import_node_path7 = require("path");
3018
+ var fileDownloadAllCommand = new import_commander29.Command("download-all").description("\uC5C5\uBB34\uC758 \uBAA8\uB4E0 \uCCA8\uBD80\uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC").argument("[project]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (\uB610\uB294 \uCCAB \uC778\uC790\uC5D0 Dooray URL)").argument("[post-number]", "\uC5C5\uBB34 \uBC88\uD638 (project\uC640 \uD568\uAED8 \uC0AC\uC6A9)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("-o, --output <dir>", "\uC800\uC7A5 \uB514\uB809\uD1A0\uB9AC", ".").action(async (project, postNumberStr, opts) => {
2508
3019
  const config = await getConfigOrThrow();
2509
3020
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2510
3021
  const spinner = startSpinner("\uCCA8\uBD80\uD30C\uC77C \uBAA9\uB85D \uC870\uD68C \uC911...");
@@ -2520,13 +3031,13 @@ var fileDownloadAllCommand = new import_commander24.Command("download-all").desc
2520
3031
  process.stdout.write("\uCCA8\uBD80\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
2521
3032
  return;
2522
3033
  }
2523
- await (0, import_promises9.mkdir)(opts.output, { recursive: true });
3034
+ await (0, import_promises10.mkdir)(opts.output, { recursive: true });
2524
3035
  const downloaded = [];
2525
3036
  for (const file of res.result) {
2526
3037
  spinner.text = `\uB2E4\uC6B4\uB85C\uB4DC \uC911: ${file.name} (${downloaded.length + 1}/${res.result.length})`;
2527
3038
  const { buffer, fileName } = await client.downloadPostFile(projectId, postId, file.id);
2528
- const outputPath = (0, import_node_path5.join)(opts.output, fileName);
2529
- await (0, import_promises9.writeFile)(outputPath, Buffer.from(buffer));
3039
+ const outputPath = (0, import_node_path7.join)(opts.output, fileName);
3040
+ await (0, import_promises10.writeFile)(outputPath, Buffer.from(buffer));
2530
3041
  downloaded.push(outputPath);
2531
3042
  }
2532
3043
  stopSpinner(true, `${downloaded.length}\uAC1C \uD30C\uC77C \uB2E4\uC6B4\uB85C\uB4DC \uC644\uB8CC`);
@@ -2537,9 +3048,9 @@ var fileDownloadAllCommand = new import_commander24.Command("download-all").desc
2537
3048
  });
2538
3049
 
2539
3050
  // src/commands/post/file/upload.ts
2540
- var import_commander25 = require("commander");
2541
- var import_node_path6 = require("path");
2542
- var fileUploadCommand = new import_commander25.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
3051
+ var import_commander30 = require("commander");
3052
+ var import_node_path8 = require("path");
3053
+ var fileUploadCommand = new import_commander30.Command("upload").description("\uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uD30C\uC77C \uACBD\uB85C (positional \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2543
3054
  const globalOpts = fileUploadCommand.optsWithGlobals();
2544
3055
  const config = await getConfigOrThrow();
2545
3056
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2594,14 +3105,14 @@ var fileUploadCommand = new import_commander25.Command("upload").description("\u
2594
3105
  process.stdout.write(`${res.result.id}
2595
3106
  `);
2596
3107
  } else {
2597
- process.stdout.write(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${(0, import_node_path6.basename)(filePath)} (ID: ${res.result.id})
3108
+ process.stdout.write(`\uD30C\uC77C \uC5C5\uB85C\uB4DC \uC644\uB8CC: ${(0, import_node_path8.basename)(filePath)} (ID: ${res.result.id})
2598
3109
  `);
2599
3110
  }
2600
3111
  });
2601
3112
 
2602
3113
  // src/commands/post/file/delete.ts
2603
- var import_commander26 = require("commander");
2604
- var fileDeleteCommand = new import_commander26.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
3114
+ var import_commander31 = require("commander");
3115
+ var fileDeleteCommand = new import_commander31.Command("delete").description("\uCCA8\uBD80\uD30C\uC77C \uC0AD\uC81C").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg2]", "\uC5C5\uBB34 \uBC88\uD638 \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C ID").argument("[arg3]", "\uD30C\uC77C ID (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <postId>", "Dooray post ID (project/post-number \uB300\uC2E0)").option("--url <url>", "Dooray \uC5C5\uBB34 URL (project/post-number \uB300\uC2E0)").option("--file-id <fileId>", "\uD30C\uC77C ID (positional \uB300\uCCB4)").action(async (arg1, arg2, arg3, opts) => {
2605
3116
  const config = await getConfigOrThrow();
2606
3117
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2607
3118
  let projectArg;
@@ -2654,7 +3165,7 @@ var fileDeleteCommand = new import_commander26.Command("delete").description("\u
2654
3165
  });
2655
3166
 
2656
3167
  // src/commands/wiki/list.ts
2657
- var import_commander27 = require("commander");
3168
+ var import_commander32 = require("commander");
2658
3169
 
2659
3170
  // src/formatters/wiki.ts
2660
3171
  function formatWikiList(wikis, opts) {
@@ -2697,7 +3208,7 @@ function formatWikiPageDetail(page, opts) {
2697
3208
  }
2698
3209
 
2699
3210
  // src/commands/wiki/list.ts
2700
- var wikiListCommand = new import_commander27.Command("list").description("\uC704\uD0A4 \uBAA9\uB85D \uC870\uD68C").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (opts) => {
3211
+ var wikiListCommand = new import_commander32.Command("list").description("\uC704\uD0A4 \uBAA9\uB85D \uC870\uD68C").option("--page <number>", "\uD398\uC774\uC9C0 \uBC88\uD638", "0").option("--size <number>", "\uD398\uC774\uC9C0 \uD06C\uAE30", "20").action(async (opts) => {
2701
3212
  const globalOpts = wikiListCommand.optsWithGlobals();
2702
3213
  const config = await getConfigOrThrow();
2703
3214
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2711,7 +3222,7 @@ var wikiListCommand = new import_commander27.Command("list").description("\uC704
2711
3222
  });
2712
3223
 
2713
3224
  // src/commands/wiki/pages.ts
2714
- var import_commander28 = require("commander");
3225
+ var import_commander33 = require("commander");
2715
3226
 
2716
3227
  // src/resolvers/wiki.ts
2717
3228
  async function resolveWiki(client, projectCode) {
@@ -2755,7 +3266,7 @@ async function resolveWikiHomePageId(client, wikiId) {
2755
3266
  }
2756
3267
 
2757
3268
  // src/commands/wiki/pages.ts
2758
- var wikiPagesCommand = new import_commander28.Command("pages").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").action(async (project, opts) => {
3269
+ var wikiPagesCommand = new import_commander33.Command("pages").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uBAA9\uB85D \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").action(async (project, opts) => {
2759
3270
  const globalOpts = wikiPagesCommand.optsWithGlobals();
2760
3271
  const config = await getConfigOrThrow();
2761
3272
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2767,8 +3278,8 @@ var wikiPagesCommand = new import_commander28.Command("pages").description("\uC7
2767
3278
  });
2768
3279
 
2769
3280
  // src/commands/wiki/page-get.ts
2770
- var import_commander29 = require("commander");
2771
- var wikiPageGetCommand = new import_commander29.Command("get").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0C1\uC138 \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
3281
+ var import_commander34 = require("commander");
3282
+ var wikiPageGetCommand = new import_commander34.Command("get").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0C1\uC138 \uC870\uD68C").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").action(async (project, pageId) => {
2772
3283
  const globalOpts = wikiPageGetCommand.optsWithGlobals();
2773
3284
  const config = await getConfigOrThrow();
2774
3285
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2780,8 +3291,8 @@ var wikiPageGetCommand = new import_commander29.Command("get").description("\uC7
2780
3291
  });
2781
3292
 
2782
3293
  // src/commands/wiki/page-create.ts
2783
- var import_commander30 = require("commander");
2784
- var wikiPageCreateCommand = new import_commander30.Command("create").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, opts) => {
3294
+ var import_commander35 = require("commander");
3295
+ var wikiPageCreateCommand = new import_commander35.Command("create").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC0DD\uC131").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").requiredOption("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9").option("--parent <page-id>", "\uBD80\uBAA8 \uD398\uC774\uC9C0 ID").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, opts) => {
2785
3296
  const globalOpts = wikiPageCreateCommand.optsWithGlobals();
2786
3297
  const config = await getConfigOrThrow();
2787
3298
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2806,8 +3317,8 @@ var wikiPageCreateCommand = new import_commander30.Command("create").description
2806
3317
  });
2807
3318
 
2808
3319
  // src/commands/wiki/page-edit.ts
2809
- var import_commander31 = require("commander");
2810
- var wikiPageEditCommand = new import_commander31.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 (\uD50C\uB798\uADF8 \uC5C6\uC73C\uBA74 $EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").option("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9 (\uC9C0\uC815 \uC2DC $EDITOR \uC0DD\uB7B5)").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, pageId, opts) => {
3320
+ var import_commander36 = require("commander");
3321
+ var wikiPageEditCommand = new import_commander36.Command("edit").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uC218\uC815 (\uD50C\uB798\uADF8 \uC5C6\uC73C\uBA74 $EDITOR)").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").argument("<page-id>", "\uD398\uC774\uC9C0 ID").option("--title <title>", "\uD398\uC774\uC9C0 \uC81C\uBAA9 (\uC9C0\uC815 \uC2DC $EDITOR \uC0DD\uB7B5)").option("--body <text>", "\uBCF8\uBB38 \uD14D\uC2A4\uD2B8 (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C (- \uC785\uB825 \uC2DC stdin\uC5D0\uC11C \uC77D\uAE30)").action(async (project, pageId, opts) => {
2811
3322
  const config = await getConfigOrThrow();
2812
3323
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
2813
3324
  const hasTitle = opts.title != null;
@@ -2860,10 +3371,10 @@ var wikiPageEditCommand = new import_commander31.Command("edit").description("\u
2860
3371
  });
2861
3372
 
2862
3373
  // src/commands/member/index.ts
2863
- var import_commander34 = require("commander");
3374
+ var import_commander40 = require("commander");
2864
3375
 
2865
3376
  // src/commands/member/get.ts
2866
- var import_commander32 = require("commander");
3377
+ var import_commander37 = require("commander");
2867
3378
 
2868
3379
  // src/formatters/member.ts
2869
3380
  function formatMemberDetail(member, opts) {
@@ -2893,9 +3404,23 @@ function formatMemberList(rows, opts) {
2893
3404
  ids: rows.map((r) => r.id)
2894
3405
  });
2895
3406
  }
3407
+ function formatMemberSearchResults(members, opts) {
3408
+ output(opts, {
3409
+ headers: ["ID", "Name", "UserCode", "Nickname", "Email"],
3410
+ rows: members.map((m) => [
3411
+ m.id,
3412
+ m.name,
3413
+ m.userCode ?? "",
3414
+ m.nickname ?? "",
3415
+ m.externalEmailAddress ?? ""
3416
+ ]),
3417
+ raw: members,
3418
+ ids: members.map((m) => m.id)
3419
+ });
3420
+ }
2896
3421
 
2897
3422
  // src/commands/member/get.ts
2898
- var memberGetCommand = new import_commander32.Command("get").description("\uBA64\uBC84 \uC0C1\uC138 \uC815\uBCF4 \uC870\uD68C (organizationMemberId)").argument("<member-id>", "\uC870\uD68C\uD560 organization member ID").action(async (memberId) => {
3423
+ var memberGetCommand = new import_commander37.Command("get").description("\uBA64\uBC84 \uC0C1\uC138 \uC815\uBCF4 \uC870\uD68C (organizationMemberId)").argument("<member-id>", "\uC870\uD68C\uD560 organization member ID").action(async (memberId) => {
2899
3424
  const globalOpts = memberGetCommand.optsWithGlobals();
2900
3425
  const config = await getConfigOrThrow();
2901
3426
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2904,8 +3429,8 @@ var memberGetCommand = new import_commander32.Command("get").description("\uBA64
2904
3429
  });
2905
3430
 
2906
3431
  // src/commands/member/list.ts
2907
- var import_commander33 = require("commander");
2908
- var memberListCommand = new import_commander33.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uBAA9\uB85D").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
3432
+ var import_commander38 = require("commander");
3433
+ var memberListCommand = new import_commander38.Command("list").description("\uD504\uB85C\uC81D\uD2B8 \uBA64\uBC84 \uBAA9\uB85D").argument("<project>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC \uB610\uB294 ID").action(async (project) => {
2909
3434
  const globalOpts = memberListCommand.optsWithGlobals();
2910
3435
  const config = await getConfigOrThrow();
2911
3436
  const client = new DoorayApiClient(config.apiKey, config.baseUrl);
@@ -2919,13 +3444,48 @@ var memberListCommand = new import_commander33.Command("list").description("\uD5
2919
3444
  );
2920
3445
  });
2921
3446
 
3447
+ // src/commands/member/search.ts
3448
+ var import_commander39 = require("commander");
3449
+ var memberSearchCommand = new import_commander39.Command("search").description("organization \uC804\uCCB4\uC5D0\uC11C \uBA64\uBC84 \uAC80\uC0C9").argument("[keyword]", "\uC774\uB984 \uAC80\uC0C9\uC5B4 (--email/--user-code \uBBF8\uC9C0\uC815 \uC2DC name\uC73C\uB85C \uAC80\uC0C9)").option("--email <email>", "\uC678\uBD80 \uC774\uBA54\uC77C (\uCF64\uB9C8 \uAD6C\uBD84 \uAC00\uB2A5, exact match)").option("--user-code <code>", "\uC0AC\uBC88 like \uAC80\uC0C9").option("--user-code-exact <code>", "\uC0AC\uBC88 exact match").option("--page <n>", "\uD398\uC774\uC9C0 (\uAE30\uBCF8 0)", "0").option("--size <n>", "\uD398\uC774\uC9C0 \uD06C\uAE30 (\uAE30\uBCF8 20, max 100)", "20").action(async (keyword, opts) => {
3450
+ const globalOpts = memberSearchCommand.optsWithGlobals();
3451
+ const filterCount = [keyword, opts.email, opts.userCode, opts.userCodeExact].filter(Boolean).length;
3452
+ if (filterCount === 0) {
3453
+ throw new DoorayCliError(
3454
+ "\uAC80\uC0C9 \uC870\uAC74\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. positional <keyword>(=name) \uB610\uB294 --email/--user-code/--user-code-exact \uC911 \uD558\uB098 \uC774\uC0C1.",
3455
+ EXIT_PARAM_ERROR
3456
+ );
3457
+ }
3458
+ if (keyword && (opts.email || opts.userCode || opts.userCodeExact)) {
3459
+ throw new DoorayCliError(
3460
+ "positional <keyword>(name)\uC640 --email/--user-code/--user-code-exact \uC635\uC158\uC740 \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00.",
3461
+ EXIT_PARAM_ERROR
3462
+ );
3463
+ }
3464
+ const config = await getConfigOrThrow();
3465
+ const client = new DoorayApiClient(config.apiKey, config.baseUrl);
3466
+ const size = Math.min(Math.max(1, Number(opts.size) || 20), 100);
3467
+ const page = Math.max(0, Number(opts.page) || 0);
3468
+ startSpinner("\uBA64\uBC84 \uAC80\uC0C9 \uC911...");
3469
+ const res = await client.searchMembers({
3470
+ ...keyword && { name: keyword },
3471
+ ...opts.email && { externalEmailAddresses: opts.email },
3472
+ ...opts.userCode && { userCode: opts.userCode },
3473
+ ...opts.userCodeExact && { userCodeExact: opts.userCodeExact },
3474
+ page,
3475
+ size
3476
+ });
3477
+ stopSpinner(true, `${res.totalCount}\uAC74 (\uC774 \uD398\uC774\uC9C0 ${res.result.length})`);
3478
+ formatMemberSearchResults(res.result, globalOpts);
3479
+ });
3480
+
2922
3481
  // src/commands/member/index.ts
2923
- var memberCommand = new import_commander34.Command("member").description("Dooray \uBA64\uBC84 \uC870\uD68C");
3482
+ var memberCommand = new import_commander40.Command("member").description("Dooray \uBA64\uBC84 \uC870\uD68C");
2924
3483
  memberCommand.addCommand(memberGetCommand);
2925
3484
  memberCommand.addCommand(memberListCommand);
3485
+ memberCommand.addCommand(memberSearchCommand);
2926
3486
 
2927
3487
  // src/commands/mail/list.ts
2928
- var import_commander35 = require("commander");
3488
+ var import_commander41 = require("commander");
2929
3489
 
2930
3490
  // src/api/imapClient.ts
2931
3491
  var import_imapflow = require("imapflow");
@@ -3033,7 +3593,7 @@ async function getMail(config, uid) {
3033
3593
 
3034
3594
  // src/commands/mail/list.ts
3035
3595
  var import_chalk5 = __toESM(require("chalk"));
3036
- var mailListCommand = new import_commander35.Command("list").description("\uBA54\uC77C \uBAA9\uB85D \uC870\uD68C").option("--unread", "\uC548\uC77D\uC740 \uBA54\uC77C\uB9CC").option("--search <keyword>", "\uC81C\uBAA9 \uAC80\uC0C9").option("--size <number>", "\uC870\uD68C \uAC1C\uC218", "20").action(async (opts) => {
3596
+ var mailListCommand = new import_commander41.Command("list").description("\uBA54\uC77C \uBAA9\uB85D \uC870\uD68C").option("--unread", "\uC548\uC77D\uC740 \uBA54\uC77C\uB9CC").option("--search <keyword>", "\uC81C\uBAA9 \uAC80\uC0C9").option("--size <number>", "\uC870\uD68C \uAC1C\uC218", "20").action(async (opts) => {
3037
3597
  const globalOpts = mailListCommand.optsWithGlobals();
3038
3598
  const config = await getConfigOrThrow();
3039
3599
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -3065,9 +3625,9 @@ var mailListCommand = new import_commander35.Command("list").description("\uBA54
3065
3625
  });
3066
3626
 
3067
3627
  // src/commands/mail/get.ts
3068
- var import_commander36 = require("commander");
3628
+ var import_commander42 = require("commander");
3069
3629
  var import_chalk6 = __toESM(require("chalk"));
3070
- var mailGetCommand = new import_commander36.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
3630
+ var mailGetCommand = new import_commander42.Command("get").description("\uBA54\uC77C \uC0C1\uC138 \uC870\uD68C").argument("<uid>", "\uBA54\uC77C UID").action(async (uid) => {
3071
3631
  const globalOpts = mailGetCommand.optsWithGlobals();
3072
3632
  const config = await getConfigOrThrow();
3073
3633
  startSpinner("\uBA54\uC77C \uC870\uD68C \uC911...");
@@ -3098,8 +3658,8 @@ ${mail.body}
3098
3658
  });
3099
3659
 
3100
3660
  // src/commands/mail/send.ts
3101
- var import_commander37 = require("commander");
3102
- var import_promises10 = require("fs/promises");
3661
+ var import_commander43 = require("commander");
3662
+ var import_promises11 = require("fs/promises");
3103
3663
 
3104
3664
  // src/api/smtpClient.ts
3105
3665
  var import_nodemailer = __toESM(require("nodemailer"));
@@ -3148,12 +3708,12 @@ async function sendMail(config, opts) {
3148
3708
  }
3149
3709
 
3150
3710
  // src/commands/mail/send.ts
3151
- var mailSendCommand = new import_commander37.Command("send").description("\uBA54\uC77C \uBC1C\uC1A1").requiredOption("--to <addresses...>", "\uBC1B\uB294\uC0AC\uB78C \uC774\uBA54\uC77C (\uBCF5\uC218 \uAC00\uB2A5)").requiredOption("--subject <title>", "\uC81C\uBAA9").option("--body <text>", "\uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--bcc <addresses...>", "\uC228\uC740\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (opts) => {
3711
+ var mailSendCommand = new import_commander43.Command("send").description("\uBA54\uC77C \uBC1C\uC1A1").requiredOption("--to <addresses...>", "\uBC1B\uB294\uC0AC\uB78C \uC774\uBA54\uC77C (\uBCF5\uC218 \uAC00\uB2A5)").requiredOption("--subject <title>", "\uC81C\uBAA9").option("--body <text>", "\uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--bcc <addresses...>", "\uC228\uC740\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (opts) => {
3152
3712
  const globalOpts = mailSendCommand.optsWithGlobals();
3153
3713
  const config = await getConfigOrThrow();
3154
3714
  let body = opts.body ?? "";
3155
3715
  if (opts.bodyFile) {
3156
- body = await (0, import_promises10.readFile)(opts.bodyFile, "utf-8");
3716
+ body = await (0, import_promises11.readFile)(opts.bodyFile, "utf-8");
3157
3717
  }
3158
3718
  if (!body) {
3159
3719
  process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
@@ -3183,8 +3743,8 @@ var mailSendCommand = new import_commander37.Command("send").description("\uBA54
3183
3743
  });
3184
3744
 
3185
3745
  // src/commands/mail/reply.ts
3186
- var import_commander38 = require("commander");
3187
- var import_promises11 = require("fs/promises");
3746
+ var import_commander44 = require("commander");
3747
+ var import_promises12 = require("fs/promises");
3188
3748
  var import_imapflow2 = require("imapflow");
3189
3749
  async function getMessageId(config, uid) {
3190
3750
  const imap = getImapConfigOrThrow(config);
@@ -3211,12 +3771,12 @@ async function getMessageId(config, uid) {
3211
3771
  await client.logout();
3212
3772
  }
3213
3773
  }
3214
- var mailReplyCommand = new import_commander38.Command("reply").description("\uBA54\uC77C \uB2F5\uC7A5").argument("<uid>", "\uC6D0\uBCF8 \uBA54\uC77C UID").option("--body <text>", "\uB2F5\uC7A5 \uBCF8\uBB38").option("--body-file <path>", "\uB2F5\uC7A5 \uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (uid, opts) => {
3774
+ var mailReplyCommand = new import_commander44.Command("reply").description("\uBA54\uC77C \uB2F5\uC7A5").argument("<uid>", "\uC6D0\uBCF8 \uBA54\uC77C UID").option("--body <text>", "\uB2F5\uC7A5 \uBCF8\uBB38").option("--body-file <path>", "\uB2F5\uC7A5 \uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option("--cc <addresses...>", "\uCC38\uC870").option("--html", "\uBCF8\uBB38\uC744 HTML\uB85C \uC804\uC1A1").action(async (uid, opts) => {
3215
3775
  const globalOpts = mailReplyCommand.optsWithGlobals();
3216
3776
  const config = await getConfigOrThrow();
3217
3777
  let body = opts.body ?? "";
3218
3778
  if (opts.bodyFile) {
3219
- body = await (0, import_promises11.readFile)(opts.bodyFile, "utf-8");
3779
+ body = await (0, import_promises12.readFile)(opts.bodyFile, "utf-8");
3220
3780
  }
3221
3781
  if (!body) {
3222
3782
  process.stderr.write("\uC624\uB958: --body \uB610\uB294 --body-file\uC744 \uC9C0\uC815\uD558\uC138\uC694\n");
@@ -3252,29 +3812,29 @@ var mailReplyCommand = new import_commander38.Command("reply").description("\uBA
3252
3812
  });
3253
3813
 
3254
3814
  // src/commands/feedback.ts
3255
- var import_commander39 = require("commander");
3815
+ var import_commander45 = require("commander");
3256
3816
  var import_node_child_process2 = require("child_process");
3257
3817
  var import_node_util = require("util");
3258
- var import_promises13 = require("fs/promises");
3259
- var import_node_os3 = require("os");
3260
- var import_node_path8 = require("path");
3818
+ var import_promises15 = require("fs/promises");
3819
+ var import_node_os4 = require("os");
3820
+ var import_node_path11 = require("path");
3261
3821
  var import_node_crypto = require("crypto");
3262
3822
  var import_prompts = require("@inquirer/prompts");
3263
3823
 
3264
3824
  // src/utils/feedback-meta.ts
3265
- var import_promises12 = require("fs/promises");
3266
- var import_node_path7 = require("path");
3825
+ var import_promises13 = require("fs/promises");
3826
+ var import_node_path9 = require("path");
3267
3827
  async function readCliVersion() {
3268
3828
  const entry = process.argv[1];
3269
- const here = entry ? (0, import_node_path7.dirname)(entry) : process.cwd();
3829
+ const here = entry ? (0, import_node_path9.dirname)(entry) : process.cwd();
3270
3830
  const candidates = [
3271
- (0, import_node_path7.join)(here, "package.json"),
3272
- (0, import_node_path7.join)(here, "..", "package.json"),
3273
- (0, import_node_path7.join)(here, "..", "..", "package.json")
3831
+ (0, import_node_path9.join)(here, "package.json"),
3832
+ (0, import_node_path9.join)(here, "..", "package.json"),
3833
+ (0, import_node_path9.join)(here, "..", "..", "package.json")
3274
3834
  ];
3275
3835
  for (const p of candidates) {
3276
3836
  try {
3277
- const raw = await (0, import_promises12.readFile)(p, "utf-8");
3837
+ const raw = await (0, import_promises13.readFile)(p, "utf-8");
3278
3838
  const pkg = JSON.parse(raw);
3279
3839
  if (pkg.name === "@bifos/dooray-cli" && typeof pkg.version === "string") {
3280
3840
  return pkg.version;
@@ -3292,6 +3852,19 @@ function collectMeta(version) {
3292
3852
  arch: process.arch
3293
3853
  };
3294
3854
  }
3855
+ function buildLastRunBlock(last) {
3856
+ return [
3857
+ "## \uC9C1\uC804 \uC2E4\uD589 (\uC790\uB3D9 \uCCA8\uBD80)",
3858
+ "",
3859
+ "```",
3860
+ `$ ${last.argv.join(" ")}`,
3861
+ last.errorMessage.replace(/```/g, "'''"),
3862
+ "```",
3863
+ "",
3864
+ `- exit code: ${last.exitCode}`,
3865
+ `- \uC2DC\uAC01: ${last.timestamp}`
3866
+ ].join("\n");
3867
+ }
3295
3868
  function buildIssueBody(userBody, meta) {
3296
3869
  return [
3297
3870
  "## \uD658\uACBD",
@@ -3306,6 +3879,32 @@ function buildIssueBody(userBody, meta) {
3306
3879
  ].join("\n");
3307
3880
  }
3308
3881
 
3882
+ // src/cache/last-run.ts
3883
+ var import_promises14 = require("fs/promises");
3884
+ var import_node_path10 = require("path");
3885
+ var import_node_os3 = require("os");
3886
+ var LAST_RUN_PATH = (0, import_node_path10.join)((0, import_node_os3.homedir)(), ".dooray", "last-run.json");
3887
+ function isLastRun(obj) {
3888
+ if (typeof obj !== "object" || obj === null) return false;
3889
+ const o = obj;
3890
+ return Array.isArray(o.argv) && o.argv.every((a) => typeof a === "string") && typeof o.exitCode === "number" && typeof o.errorMessage === "string" && typeof o.timestamp === "string";
3891
+ }
3892
+ async function readLastRun() {
3893
+ try {
3894
+ const raw = await (0, import_promises14.readFile)(LAST_RUN_PATH, "utf-8");
3895
+ const parsed = JSON.parse(raw);
3896
+ return isLastRun(parsed) ? parsed : null;
3897
+ } catch {
3898
+ return null;
3899
+ }
3900
+ }
3901
+ async function writeLastRun(data) {
3902
+ await (0, import_promises14.mkdir)((0, import_node_path10.join)((0, import_node_os3.homedir)(), ".dooray"), { recursive: true });
3903
+ const tmp2 = LAST_RUN_PATH + ".tmp";
3904
+ await (0, import_promises14.writeFile)(tmp2, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
3905
+ await (0, import_promises14.rename)(tmp2, LAST_RUN_PATH);
3906
+ }
3907
+
3309
3908
  // src/commands/feedback.ts
3310
3909
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
3311
3910
  var TARGET_REPO = "jon890/dooray-cli";
@@ -3321,18 +3920,36 @@ async function ensureGhInstalled() {
3321
3920
  }
3322
3921
  async function readBody(opts) {
3323
3922
  if (opts.body) return opts.body;
3324
- if (opts.bodyFile) return await (0, import_promises13.readFile)(opts.bodyFile, "utf-8");
3923
+ if (opts.bodyFile) return await (0, import_promises15.readFile)(opts.bodyFile, "utf-8");
3325
3924
  return void 0;
3326
3925
  }
3327
- var feedbackCommand = new import_commander39.Command("feedback").description("dooray-cli\uC5D0 \uB300\uD55C GitHub issue \uB4F1\uB85D (gh CLI \uC704\uC784)").option("--title <text>", "\uC774\uC288 \uC81C\uBAA9 (\uC5C6\uC73C\uBA74 \uC778\uD130\uB799\uD2F0\uBE0C)").option("--body <text>", "\uC774\uC288 \uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option(
3926
+ var feedbackCommand = new import_commander45.Command("feedback").description("dooray-cli\uC5D0 \uB300\uD55C GitHub issue \uB4F1\uB85D (gh CLI \uC704\uC784)").option("--title <text>", "\uC774\uC288 \uC81C\uBAA9 (\uC5C6\uC73C\uBA74 \uC778\uD130\uB799\uD2F0\uBE0C)").option("--body <text>", "\uC774\uC288 \uBCF8\uBB38").option("--body-file <path>", "\uBCF8\uBB38 \uD30C\uC77C \uACBD\uB85C").option(
3328
3927
  "--label <name>",
3329
3928
  "\uB77C\uBCA8 (\uBC18\uBCF5 \uAC00\uB2A5)",
3330
3929
  (value, prev) => [...prev, value],
3331
3930
  []
3332
- ).option("--dry-run", "gh \uD638\uCD9C \uC5C6\uC774 \uBCF8\uBB38\uB9CC \uBBF8\uB9AC\uBCF4\uAE30").action(async (opts) => {
3931
+ ).option("--last", "\uC9C1\uC804 \uC2E4\uD589\uD55C dooray \uBA85\uB839\uC758 argv + \uC5D0\uB7EC\uB97C \uBCF8\uBB38 \uC0C1\uB2E8\uC5D0 \uC790\uB3D9 \uCCA8\uBD80").option("--dry-run", "gh \uD638\uCD9C \uC5C6\uC774 \uBCF8\uBB38\uB9CC \uBBF8\uB9AC\uBCF4\uAE30").action(async (opts) => {
3333
3932
  let title = opts.title;
3334
3933
  let userBody = await readBody(opts);
3335
3934
  let labels = [...opts.label];
3935
+ if (opts.last) {
3936
+ const last = await readLastRun();
3937
+ if (!last) {
3938
+ throw new DoorayCliError(
3939
+ "\uAE30\uB85D\uB41C \uC9C1\uC804 \uC2E4\uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. config.json\uC5D0 trackLastRun: true \uC124\uC815 \uD6C4 dooray \uBA85\uB839 \uC2E4\uD589 \uC2DC \uC790\uB3D9 \uAE30\uB85D\uB429\uB2C8\uB2E4.\n \uC124\uC815: dooray config set track-last-run true",
3940
+ EXIT_PARAM_ERROR
3941
+ );
3942
+ }
3943
+ const lastBlock = buildLastRunBlock(last);
3944
+ if (userBody == null) {
3945
+ userBody = await (0, import_prompts.editor)({
3946
+ message: "\uBCF8\uBB38 \uC791\uC131 ($EDITOR\uAC00 \uC5F4\uB9BC)",
3947
+ default: lastBlock + "\n\n"
3948
+ });
3949
+ } else {
3950
+ userBody = lastBlock + "\n\n" + userBody;
3951
+ }
3952
+ }
3336
3953
  if (!title) {
3337
3954
  title = await (0, import_prompts.input)({ message: "\uC774\uC288 \uC81C\uBAA9" });
3338
3955
  }
@@ -3397,8 +4014,8 @@ var feedbackCommand = new import_commander39.Command("feedback").description("do
3397
4014
  }
3398
4015
  }
3399
4016
  await ensureGhInstalled();
3400
- const bodyFile = (0, import_node_path8.join)((0, import_node_os3.tmpdir)(), `dooray-feedback-${(0, import_node_crypto.randomUUID)()}.md`);
3401
- await (0, import_promises13.writeFile)(bodyFile, issueBody);
4017
+ const bodyFile = (0, import_node_path11.join)((0, import_node_os4.tmpdir)(), `dooray-feedback-${(0, import_node_crypto.randomUUID)()}.md`);
4018
+ await (0, import_promises15.writeFile)(bodyFile, issueBody);
3402
4019
  try {
3403
4020
  const args = [
3404
4021
  "issue",
@@ -3425,27 +4042,68 @@ gh \uC778\uC99D \uC548 \uB418\uC5B4 \uC788\uC73C\uBA74: gh auth login`,
3425
4042
  EXIT_PARAM_ERROR
3426
4043
  );
3427
4044
  } finally {
3428
- await (0, import_promises13.unlink)(bodyFile).catch(() => {
4045
+ await (0, import_promises15.unlink)(bodyFile).catch(() => {
3429
4046
  });
3430
4047
  }
3431
4048
  });
3432
4049
 
4050
+ // src/utils/argv-sanitize.ts
4051
+ var KEY_VALUE_PATTERNS = [
4052
+ /^(--api-key|--token|--password)=(.+)$/
4053
+ ];
4054
+ var SEPARATED_KEYS = /* @__PURE__ */ new Set(["--api-key", "--token", "--password"]);
4055
+ function sanitizeArgv(argv) {
4056
+ const out = [];
4057
+ for (let i = 0; i < argv.length; i++) {
4058
+ const a = argv[i];
4059
+ let kvMatch = null;
4060
+ for (const re of KEY_VALUE_PATTERNS) {
4061
+ const m = re.exec(a);
4062
+ if (m) {
4063
+ kvMatch = m;
4064
+ break;
4065
+ }
4066
+ }
4067
+ if (kvMatch) {
4068
+ out.push(`${kvMatch[1]}=***`);
4069
+ continue;
4070
+ }
4071
+ if (SEPARATED_KEYS.has(a)) {
4072
+ out.push(a);
4073
+ if (i + 1 < argv.length) {
4074
+ out.push("***");
4075
+ i++;
4076
+ }
4077
+ continue;
4078
+ }
4079
+ if (/^Authorization\s*:/i.test(a) || /^Bearer\s+\S+/.test(a)) {
4080
+ out.push(a.replace(/(Authorization\s*:\s*).+/i, "$1***").replace(/^(Bearer\s+).+/i, "$1***"));
4081
+ continue;
4082
+ }
4083
+ out.push(a);
4084
+ }
4085
+ return out;
4086
+ }
4087
+
3433
4088
  // src/index.ts
3434
- var program = new import_commander40.Command();
3435
- program.name("dooray").description("Dooray REST API CLI").version("0.5.4").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
4089
+ var program = new import_commander46.Command();
4090
+ program.name("dooray").description("Dooray REST API CLI").version("0.6.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
3436
4091
  program.hook("preAction", () => {
3437
4092
  const opts = program.opts();
3438
4093
  if (opts.color === false || process.env.NO_COLOR) {
3439
4094
  import_chalk7.default.level = 0;
3440
4095
  }
4096
+ if (opts.json || opts.quiet) {
4097
+ setQuiet(true);
4098
+ }
3441
4099
  });
3442
- var projectCommand = new import_commander40.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
4100
+ var projectCommand = new import_commander46.Command("project").description("\uD504\uB85C\uC81D\uD2B8 \uAD00\uB828 \uBA85\uB839");
3443
4101
  projectCommand.addCommand(projectListCommand);
3444
4102
  projectCommand.addCommand(projectMembersCommand);
3445
4103
  projectCommand.addCommand(projectWorkflowsCommand);
3446
4104
  projectCommand.addCommand(projectGroupsCommand);
3447
4105
  projectCommand.addCommand(projectTagsCommand);
3448
- var postCommand = new import_commander40.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
4106
+ var postCommand = new import_commander46.Command("post").description("\uC5C5\uBB34 \uAD00\uB828 \uBA85\uB839");
3449
4107
  postCommand.addCommand(postListCommand);
3450
4108
  postCommand.addCommand(postSearchCommand);
3451
4109
  postCommand.addCommand(postGetCommand);
@@ -3453,29 +4111,30 @@ postCommand.addCommand(postEditCommand);
3453
4111
  postCommand.addCommand(postCreateCommand);
3454
4112
  postCommand.addCommand(postDoneCommand);
3455
4113
  postCommand.addCommand(postWorkflowCommand);
3456
- var commentCommand = new import_commander40.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
4114
+ var commentCommand = new import_commander46.Command("comment").description("\uB313\uAE00 \uAD00\uB828 \uBA85\uB839");
3457
4115
  commentCommand.addCommand(commentListCommand);
3458
4116
  commentCommand.addCommand(commentLatestCommand);
3459
4117
  commentCommand.addCommand(commentAddCommand);
3460
4118
  commentCommand.addCommand(commentEditCommand);
3461
4119
  commentCommand.addCommand(commentDeleteCommand);
4120
+ commentCommand.addCommand(commentFileCommand);
3462
4121
  postCommand.addCommand(commentCommand);
3463
- var fileCommand = new import_commander40.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
4122
+ var fileCommand = new import_commander46.Command("file").description("\uCCA8\uBD80\uD30C\uC77C \uAD00\uB828 \uBA85\uB839");
3464
4123
  fileCommand.addCommand(fileListCommand);
3465
4124
  fileCommand.addCommand(fileDownloadCommand);
3466
4125
  fileCommand.addCommand(fileDownloadAllCommand);
3467
4126
  fileCommand.addCommand(fileUploadCommand);
3468
4127
  fileCommand.addCommand(fileDeleteCommand);
3469
4128
  postCommand.addCommand(fileCommand);
3470
- var wikiCommand = new import_commander40.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
4129
+ var wikiCommand = new import_commander46.Command("wiki").description("\uC704\uD0A4 \uAD00\uB828 \uBA85\uB839");
3471
4130
  wikiCommand.addCommand(wikiListCommand);
3472
4131
  wikiCommand.addCommand(wikiPagesCommand);
3473
- var wikiPageCommand = new import_commander40.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
4132
+ var wikiPageCommand = new import_commander46.Command("page").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uAD00\uB828 \uBA85\uB839");
3474
4133
  wikiPageCommand.addCommand(wikiPageGetCommand);
3475
4134
  wikiPageCommand.addCommand(wikiPageCreateCommand);
3476
4135
  wikiPageCommand.addCommand(wikiPageEditCommand);
3477
4136
  wikiCommand.addCommand(wikiPageCommand);
3478
- var mailCommand = new import_commander40.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
4137
+ var mailCommand = new import_commander46.Command("mail").description("\uBA54\uC77C \uAD00\uB828 \uBA85\uB839");
3479
4138
  mailCommand.addCommand(mailListCommand);
3480
4139
  mailCommand.addCommand(mailGetCommand);
3481
4140
  mailCommand.addCommand(mailSendCommand);
@@ -3490,11 +4149,24 @@ program.addCommand(postCommand);
3490
4149
  program.addCommand(wikiCommand);
3491
4150
  program.addCommand(mailCommand);
3492
4151
  program.addCommand(feedbackCommand);
3493
- program.parseAsync().catch((err) => {
3494
- if (err instanceof DoorayCliError) {
3495
- process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${err.message}`) + "\n");
3496
- process.exit(err.exitCode);
4152
+ program.parseAsync().catch(async (err) => {
4153
+ const errorMessage = err instanceof Error ? err.message : String(err);
4154
+ const exitCode = err instanceof DoorayCliError ? err.exitCode : 1;
4155
+ try {
4156
+ const config = await getConfig();
4157
+ const sanitized = sanitizeArgv(process.argv.slice(2));
4158
+ const firstNonFlag = sanitized.find((a) => !a.startsWith("-"));
4159
+ const isFeedbackCommand = firstNonFlag === "feedback";
4160
+ if (config?.trackLastRun && !isFeedbackCommand) {
4161
+ await writeLastRun({
4162
+ argv: ["dooray", ...sanitized],
4163
+ exitCode,
4164
+ errorMessage,
4165
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4166
+ });
4167
+ }
4168
+ } catch {
3497
4169
  }
3498
- process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${err.message}`) + "\n");
3499
- process.exit(1);
4170
+ process.stderr.write(import_chalk7.default.red(`\uC624\uB958: ${errorMessage}`) + "\n");
4171
+ process.exit(exitCode);
3500
4172
  });