@kodocagent/cli 0.6.2 → 0.7.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
@@ -9739,9 +9739,9 @@ var require_load = __commonJS({
9739
9739
  var require_lib3 = __commonJS({
9740
9740
  "../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/index.js"(exports, module) {
9741
9741
  "use strict";
9742
- function JSZip7() {
9743
- if (!(this instanceof JSZip7)) {
9744
- return new JSZip7();
9742
+ function JSZip8() {
9743
+ if (!(this instanceof JSZip8)) {
9744
+ return new JSZip8();
9745
9745
  }
9746
9746
  if (arguments.length) {
9747
9747
  throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
@@ -9750,7 +9750,7 @@ var require_lib3 = __commonJS({
9750
9750
  this.comment = null;
9751
9751
  this.root = "";
9752
9752
  this.clone = function() {
9753
- var newObj = new JSZip7();
9753
+ var newObj = new JSZip8();
9754
9754
  for (var i in this) {
9755
9755
  if (typeof this[i] !== "function") {
9756
9756
  newObj[i] = this[i];
@@ -9759,16 +9759,16 @@ var require_lib3 = __commonJS({
9759
9759
  return newObj;
9760
9760
  };
9761
9761
  }
9762
- JSZip7.prototype = require_object();
9763
- JSZip7.prototype.loadAsync = require_load();
9764
- JSZip7.support = require_support();
9765
- JSZip7.defaults = require_defaults();
9766
- JSZip7.version = "3.10.1";
9767
- JSZip7.loadAsync = function(content, options) {
9768
- return new JSZip7().loadAsync(content, options);
9762
+ JSZip8.prototype = require_object();
9763
+ JSZip8.prototype.loadAsync = require_load();
9764
+ JSZip8.support = require_support();
9765
+ JSZip8.defaults = require_defaults();
9766
+ JSZip8.version = "3.10.1";
9767
+ JSZip8.loadAsync = function(content, options) {
9768
+ return new JSZip8().loadAsync(content, options);
9769
9769
  };
9770
- JSZip7.external = require_external();
9771
- module.exports = JSZip7;
9770
+ JSZip8.external = require_external();
9771
+ module.exports = JSZip8;
9772
9772
  }
9773
9773
  });
9774
9774
 
@@ -9903,6 +9903,31 @@ function redactText(text3) {
9903
9903
  }
9904
9904
  return { text: result, findings };
9905
9905
  }
9906
+ function redactRanges(text3) {
9907
+ if (!text3) return [];
9908
+ const ranges = [];
9909
+ for (const { type, re, mask } of PATTERNS) {
9910
+ for (const m of text3.matchAll(new RegExp(re.source, re.flags))) {
9911
+ if (m.index === void 0) continue;
9912
+ ranges.push({
9913
+ start: m.index,
9914
+ end: m.index + m[0].length,
9915
+ replacement: mask(m[0]),
9916
+ type
9917
+ });
9918
+ }
9919
+ }
9920
+ ranges.sort((a, b) => a.start - b.start || b.end - a.end);
9921
+ const out = [];
9922
+ let lastEnd = -1;
9923
+ for (const r of ranges) {
9924
+ if (r.start >= lastEnd) {
9925
+ out.push(r);
9926
+ lastEnd = r.end;
9927
+ }
9928
+ }
9929
+ return out;
9930
+ }
9906
9931
 
9907
9932
  // ../core/dist/index.js
9908
9933
  import { stepCountIs, streamText } from "ai";
@@ -9977,6 +10002,12 @@ var LAW_RULES_SECTION = `## \uBC95\uB839 \uADDC\uCE59
9977
10002
  2. \uBC95\uB839 \uD604\uD589 \uC5EC\uBD80\uB294 MCP \uBC95\uB839 \uD234(\`mcp__korean-law__*\`)\uB85C \uD655\uC778\uD55C \uD6C4 \uC778\uC6A9\uD558\uC138\uC694.
9978
10003
  3. MCP \uBC95\uB839 \uD234\uB85C \uD655\uC778\uD558\uC9C0 \uBABB\uD55C \uBC95\uB839 \uB0B4\uC6A9\uC740 \uBC18\uB4DC\uC2DC "\u203B \uD604\uD589 \uC5EC\uBD80\uB97C \uD655\uC778\uD558\uC9C0 \uC54A\uC740 \uB0B4\uC6A9\uC785\uB2C8\uB2E4"\uB77C\uACE0 \uBA85\uC2DC\uD558\uC138\uC694.
9979
10004
  4. \uBC95\uB839 \uD574\uC11D\uC740 \uCC38\uACE0\uC6A9\uC774\uBA70 \uBC95\uC801 \uD6A8\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC911\uC694\uD55C \uC0AC\uD56D\uC740 \uC804\uBB38\uAC00\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.`;
10005
+ var SELF_VERIFY_PROMPT = `[\uC790\uB3D9 \uAC80\uC99D \uB2E8\uACC4] \uBC29\uAE08 \uBB38\uC11C\uB97C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4. \uC791\uC5C5\uC744 \uB9C8\uCE58\uAE30 \uC804\uC5D0 \uBC18\uB4DC\uC2DC \uB2E4\uC74C\uC744 \uC218\uD589\uD558\uC138\uC694.
10006
+
10007
+ 1. \`read_document\`\uB85C \uBCC0\uACBD\uB41C \uBB38\uC11C(\uB610\uB294 \uAD00\uB828 \uBD80\uBD84)\uB97C **\uB2E4\uC2DC \uC77D\uC73C\uC138\uC694**.
10008
+ 2. \uC0AC\uC6A9\uC790\uC758 **\uC6D0\uB798 \uC694\uCCAD**\uC744 \uD55C \uD56D\uBAA9\uC529 \uB300\uC870\uD558\uC138\uC694 \u2014 "\uBAA8\uB450/\uC804\uBD80/\uC77C\uAD04"\uC774\uBA74 \uBAA8\uB4E0 \uD56D\uBAA9\uC774 \uCC98\uB9AC\uB410\uB294\uC9C0, \uAC12\xB7\uD615\uC2DD\xB7\uB300\uC0C1 \uC704\uCE58\uAC00 \uC815\uD655\uD55C\uC9C0, \uBE60\uC9C0\uAC70\uB098 \uC798\uBABB \uBC18\uC601\uB41C \uBD80\uBD84\uC774 \uC5C6\uB294\uC9C0 \uD655\uC778\uD569\uB2C8\uB2E4.
10009
+ 3. \uB204\uB77D\xB7\uC624\uB958\uAC00 \uC788\uC73C\uBA74 \uC989\uC2DC \`propose_*\` \uB3C4\uAD6C\uB85C **\uCD94\uAC00 \uC218\uC815\uC548\uC744 \uC81C\uC548**\uD558\uC138\uC694. \uB2E8, \uC0AC\uC6A9\uC790\uAC00 \uC774\uBBF8 \uAC70\uC808\uD55C \uC81C\uC548\uC740 \uB2E4\uC2DC \uC62C\uB9AC\uC9C0 \uB9C8\uC138\uC694.
10010
+ 4. \uBAA8\uB4E0 \uD56D\uBAA9\uC774 \uC815\uD655\uD788 \uBC18\uC601\uB410\uC73C\uBA74 \uCD94\uAC00 \uB3C4\uAD6C \uD638\uCD9C \uC5C6\uC774 \`\uAC80\uC99D \uC644\uB8CC: <\uD55C \uC904 \uC694\uC57D>\` \uD615\uC2DD\uC73C\uB85C\uB9CC \uB2F5\uD558\uC138\uC694.`;
9980
10011
  function buildDynamicContext(ctx) {
9981
10012
  const lines = ["## \uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8"];
9982
10013
  lines.push(`- **\uC791\uC5C5 \uB514\uB809\uD130\uB9AC**: \`${ctx.cwd}\``);
@@ -10077,6 +10108,20 @@ function compactMessages(messages, maxTokens) {
10077
10108
  }
10078
10109
  return result;
10079
10110
  }
10111
+ var EDITING_TOOLS = /* @__PURE__ */ new Set([
10112
+ "propose_edit",
10113
+ "propose_find_replace",
10114
+ "propose_cell_edit",
10115
+ "propose_redact_pii",
10116
+ "propose_form_fill",
10117
+ "propose_table_structure",
10118
+ "propose_form_object",
10119
+ "propose_sheet_edit",
10120
+ "write_new_document",
10121
+ "write_new_spreadsheet",
10122
+ "restore_backup"
10123
+ ]);
10124
+ var MAX_SELF_VERIFY_ROUNDS = 1;
10080
10125
  var AgentSession = class {
10081
10126
  constructor(opts) {
10082
10127
  this.opts = opts;
@@ -10136,110 +10181,129 @@ var AgentSession = class {
10136
10181
  await store.appendUser(userMessage);
10137
10182
  const userMsg = { role: "user", content: userMessage };
10138
10183
  this.messages.push(userMsg);
10139
- const system = buildSystemPrompt({
10140
- cwd: this.opts.cwd,
10141
- mcpServers: this.opts.mcpServers ?? [],
10142
- openDocuments: this.openDocuments,
10143
- toolNames: tools.toolNames
10144
- });
10145
10184
  const aiSdkTools = tools.toAiSdkTools();
10146
- this.messages = compactMessages(this.messages, config.maxContextTokens);
10185
+ const selfVerifyEnabled = process.env.KODOC_SELF_VERIFY !== "0";
10186
+ let verifyRounds = 0;
10187
+ let totalInputTokens = 0;
10188
+ let totalOutputTokens = 0;
10189
+ let sawFinish = false;
10147
10190
  try {
10148
- const result = streamText({
10149
- model,
10150
- system,
10151
- messages: this.messages,
10152
- tools: aiSdkTools,
10153
- // 멀티스텝 정지 조건: maxSteps번 툴콜 후 중단 (AI SDK v6 실제 API)
10154
- stopWhen: stepCountIs(config.maxSteps),
10155
- abortSignal: signal,
10156
- // 샘플링 파라미터 미설정 (SPEC §3, §5 불변 원칙)
10157
- onError: () => {
10158
- }
10159
- });
10160
- for await (const part of result.fullStream) {
10161
- if (signal.aborted) break;
10162
- while (this.pendingApprovalEvents.length > 0) {
10163
- const proposal = this.pendingApprovalEvents.shift();
10164
- yield { type: "approval-required", proposal };
10165
- }
10166
- switch (part.type) {
10167
- case "text-delta": {
10168
- yield { type: "text-delta", text: part.text };
10169
- break;
10191
+ while (true) {
10192
+ const system = buildSystemPrompt({
10193
+ cwd: this.opts.cwd,
10194
+ mcpServers: this.opts.mcpServers ?? [],
10195
+ openDocuments: this.openDocuments,
10196
+ toolNames: tools.toolNames
10197
+ });
10198
+ this.messages = compactMessages(this.messages, config.maxContextTokens);
10199
+ let editedThisRound = false;
10200
+ const result = streamText({
10201
+ model,
10202
+ system,
10203
+ messages: this.messages,
10204
+ tools: aiSdkTools,
10205
+ // 멀티스텝 정지 조건: maxSteps번 툴콜 후 중단 (AI SDK v6 실제 API)
10206
+ stopWhen: stepCountIs(config.maxSteps),
10207
+ abortSignal: signal,
10208
+ // 샘플링 파라미터 미설정 (SPEC §3, §5 불변 원칙)
10209
+ onError: () => {
10170
10210
  }
10171
- case "tool-call": {
10172
- yield {
10173
- type: "tool-call",
10174
- toolName: part.toolName,
10175
- args: part.input,
10176
- callId: part.toolCallId
10177
- };
10178
- try {
10179
- const inp = part.input;
10180
- if (part.toolName === "read_document") {
10181
- this.recordOpenDocument(inp.path);
10182
- } else if (part.toolName === "compare_documents") {
10183
- this.recordOpenDocument(inp.pathA);
10184
- this.recordOpenDocument(inp.pathB);
10185
- } else if (part.toolName === "write_new_document" || part.toolName === "write_new_spreadsheet") {
10186
- this.recordOpenDocument(inp.path);
10211
+ });
10212
+ for await (const part of result.fullStream) {
10213
+ if (signal.aborted) break;
10214
+ while (this.pendingApprovalEvents.length > 0) {
10215
+ const proposal = this.pendingApprovalEvents.shift();
10216
+ yield { type: "approval-required", proposal };
10217
+ }
10218
+ switch (part.type) {
10219
+ case "text-delta": {
10220
+ yield { type: "text-delta", text: part.text };
10221
+ break;
10222
+ }
10223
+ case "tool-call": {
10224
+ yield {
10225
+ type: "tool-call",
10226
+ toolName: part.toolName,
10227
+ args: part.input,
10228
+ callId: part.toolCallId
10229
+ };
10230
+ if (EDITING_TOOLS.has(part.toolName)) editedThisRound = true;
10231
+ try {
10232
+ const inp = part.input;
10233
+ if (part.toolName === "read_document") {
10234
+ this.recordOpenDocument(inp.path);
10235
+ } else if (part.toolName === "compare_documents") {
10236
+ this.recordOpenDocument(inp.pathA);
10237
+ this.recordOpenDocument(inp.pathB);
10238
+ } else if (part.toolName === "write_new_document" || part.toolName === "write_new_spreadsheet") {
10239
+ this.recordOpenDocument(inp.path);
10240
+ }
10241
+ } catch {
10187
10242
  }
10188
- } catch {
10243
+ break;
10189
10244
  }
10190
- break;
10191
- }
10192
- case "tool-result": {
10193
- const isError = false;
10194
- yield {
10195
- type: "tool-result",
10196
- callId: part.toolCallId,
10197
- result: part.output,
10198
- isError
10199
- };
10200
- await store.appendToolResult(part.toolCallId, part.output, isError);
10201
- break;
10202
- }
10203
- case "tool-error": {
10204
- yield {
10205
- type: "tool-result",
10206
- callId: part.toolCallId,
10207
- result: String(part.error),
10208
- isError: true
10209
- };
10210
- await store.appendToolResult(part.toolCallId, String(part.error), true);
10211
- break;
10212
- }
10213
- case "finish": {
10214
- const usage = part.totalUsage;
10215
- yield {
10216
- type: "turn-complete",
10217
- usage: usage ? {
10218
- inputTokens: usage.inputTokens ?? 0,
10219
- outputTokens: usage.outputTokens ?? 0
10220
- } : void 0
10221
- };
10222
- break;
10245
+ case "tool-result": {
10246
+ const isError = false;
10247
+ yield {
10248
+ type: "tool-result",
10249
+ callId: part.toolCallId,
10250
+ result: part.output,
10251
+ isError
10252
+ };
10253
+ await store.appendToolResult(part.toolCallId, part.output, isError);
10254
+ break;
10255
+ }
10256
+ case "tool-error": {
10257
+ yield {
10258
+ type: "tool-result",
10259
+ callId: part.toolCallId,
10260
+ result: String(part.error),
10261
+ isError: true
10262
+ };
10263
+ await store.appendToolResult(part.toolCallId, String(part.error), true);
10264
+ break;
10265
+ }
10266
+ case "finish": {
10267
+ const usage = part.totalUsage;
10268
+ if (usage) {
10269
+ totalInputTokens += usage.inputTokens ?? 0;
10270
+ totalOutputTokens += usage.outputTokens ?? 0;
10271
+ }
10272
+ sawFinish = true;
10273
+ break;
10274
+ }
10275
+ case "error": {
10276
+ const errPart = part;
10277
+ const message = errPart.error instanceof Error ? errPart.error.message : String(errPart.error);
10278
+ yield { type: "error", message, recoverable: false };
10279
+ break;
10280
+ }
10281
+ default:
10282
+ break;
10223
10283
  }
10224
- case "error": {
10225
- const errPart = part;
10226
- const message = errPart.error instanceof Error ? errPart.error.message : String(errPart.error);
10227
- yield { type: "error", message, recoverable: false };
10228
- break;
10284
+ }
10285
+ try {
10286
+ const response = await result.response;
10287
+ for (const msg of response.messages) {
10288
+ const modelMsg = msg;
10289
+ this.messages.push(modelMsg);
10290
+ await store.appendAssistant(modelMsg);
10229
10291
  }
10230
- default:
10231
- break;
10292
+ } catch {
10232
10293
  }
10233
- }
10234
- try {
10235
- const response = await result.response;
10236
- for (const msg of response.messages) {
10237
- const modelMsg = msg;
10238
- this.messages.push(modelMsg);
10239
- await store.appendAssistant(modelMsg);
10294
+ if (selfVerifyEnabled && editedThisRound && verifyRounds < MAX_SELF_VERIFY_ROUNDS && !signal.aborted) {
10295
+ verifyRounds++;
10296
+ const verifyMsg = { role: "user", content: SELF_VERIFY_PROMPT };
10297
+ this.messages.push(verifyMsg);
10298
+ await store.appendUser(SELF_VERIFY_PROMPT);
10299
+ continue;
10240
10300
  }
10241
- } catch {
10301
+ break;
10242
10302
  }
10303
+ yield {
10304
+ type: "turn-complete",
10305
+ usage: sawFinish ? { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } : void 0
10306
+ };
10243
10307
  } catch (err) {
10244
10308
  if (signal.aborted) {
10245
10309
  return;
@@ -10934,25 +10998,30 @@ var ToolRegistry = class {
10934
10998
  // ../doc-tools/dist/index.js
10935
10999
  import { realpath } from "fs/promises";
10936
11000
  import { basename, dirname as dirname2, isAbsolute, join as join3, normalize, relative, resolve } from "path";
11001
+ import { isOldHwpFile, isZipFile } from "kordoc";
10937
11002
  import { copyFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile3, rename, rm, stat as stat2, writeFile as writeFile2 } from "fs/promises";
10938
11003
  import { basename as basename2, dirname as dirname22, extname, join as join22 } from "path";
10939
11004
  import { createTwoFilesPatch } from "diff";
10940
11005
  import { readdir as readdir22, readFile as readFile22, stat as stat22 } from "fs/promises";
10941
11006
  import { basename as basename3, extname as extname2, join as join32 } from "path";
10942
- var import_jszip = __toESM(require_lib3(), 1);
10943
- var import_jszip2 = __toESM(require_lib3(), 1);
10944
- var import_jszip3 = __toESM(require_lib3(), 1);
10945
- var import_jszip4 = __toESM(require_lib3(), 1);
10946
11007
  import { z as z3 } from "zod";
10947
11008
  import { readFile as readFile32 } from "fs/promises";
10948
11009
  import { blocksToMarkdown, compare } from "kordoc";
10949
11010
  import { z as z22 } from "zod";
11011
+ import { extname as extname3 } from "path";
11012
+ var import_jszip = __toESM(require_lib3(), 1);
11013
+ var import_jszip2 = __toESM(require_lib3(), 1);
11014
+ var import_jszip3 = __toESM(require_lib3(), 1);
11015
+ var import_jszip4 = __toESM(require_lib3(), 1);
11016
+ var import_jszip5 = __toESM(require_lib3(), 1);
11017
+ import { markdownToPdf, renderHtml } from "kordoc";
11018
+ import { z as z32 } from "zod";
11019
+ import { readFile as readFile4 } from "fs/promises";
11020
+ import { isZipFile as isZipFile2, parse as kordocParse } from "kordoc";
10950
11021
  import { readFile as readFile5 } from "fs/promises";
10951
11022
  import { extname as extname4 } from "path";
11023
+ import { scanSectionXml } from "kordoc";
10952
11024
  import { z as z4 } from "zod";
10953
- import { readFile as readFile4 } from "fs/promises";
10954
- import { extname as extname3 } from "path";
10955
- import { z as z32 } from "zod";
10956
11025
  import { readFile as readFile6 } from "fs/promises";
10957
11026
  import { extname as extname5 } from "path";
10958
11027
  import { z as z5 } from "zod";
@@ -10961,8 +11030,12 @@ import { extname as extname6, join as join4, relative as relative2 } from "path"
10961
11030
  import { z as z6 } from "zod";
10962
11031
  import { readFile as readFile7 } from "fs/promises";
10963
11032
  import { extname as extname7 } from "path";
10964
- import { compare as compare2, parse, patchHwp, patchHwpx } from "kordoc";
11033
+ import { applySplices, buildParagraphSplices, scanSectionXml as scanSectionXml2 } from "kordoc";
10965
11034
  import { z as z7 } from "zod";
11035
+ import { readFile as readFile8 } from "fs/promises";
11036
+ import { extname as extname8 } from "path";
11037
+ import { compare as compare2, patchHwp, patchHwpx } from "kordoc";
11038
+ import { z as z8 } from "zod";
10966
11039
  import {
10967
11040
  Document,
10968
11041
  HeadingLevel,
@@ -10974,57 +11047,60 @@ import {
10974
11047
  TextRun,
10975
11048
  WidthType
10976
11049
  } from "docx";
10977
- import { readFile as readFile8 } from "fs/promises";
10978
- import { extname as extname8 } from "path";
10979
- import { parse as parse2 } from "kordoc";
10980
- import { z as z8 } from "zod";
10981
11050
  import { readFile as readFile9 } from "fs/promises";
10982
11051
  import { extname as extname9 } from "path";
10983
- import { extractFormFields, parse as parse3, patchHwpx as patchHwpx2 } from "kordoc";
11052
+ import { applySplices as applySplices3, buildRangeSplices as buildRangeSplices2, scanSectionXml as scanSectionXml4 } from "kordoc";
10984
11053
  import { z as z9 } from "zod";
11054
+ import { applySplices as applySplices2, buildRangeSplices, scanSectionXml as scanSectionXml3 } from "kordoc";
10985
11055
  import { readFile as readFile10 } from "fs/promises";
10986
- import { basename as basename4, extname as extname10 } from "path";
10987
- var import_jszip5 = __toESM(require_lib3(), 1);
10988
- var import_jszip6 = __toESM(require_lib3(), 1);
11056
+ import { extname as extname10 } from "path";
11057
+ import { extractFormSchema, fillHwpx } from "kordoc";
10989
11058
  import { z as z10 } from "zod";
10990
11059
  import { readFile as readFile11 } from "fs/promises";
10991
- import { extname as extname11 } from "path";
10992
- import ExcelJS from "exceljs";
11060
+ import { basename as basename4, extname as extname11 } from "path";
11061
+ var import_jszip6 = __toESM(require_lib3(), 1);
11062
+ var import_jszip7 = __toESM(require_lib3(), 1);
11063
+ import { scanSectionXml as scanSectionXml5 } from "kordoc";
10993
11064
  import { z as z11 } from "zod";
10994
11065
  import { readFile as readFile12 } from "fs/promises";
10995
11066
  import { extname as extname12 } from "path";
10996
- import { parse as parse4 } from "kordoc";
11067
+ import ExcelJS from "exceljs";
10997
11068
  import { z as z12 } from "zod";
10998
- import { readFile as readFile13, stat as stat4 } from "fs/promises";
11069
+ import { readFile as readFile13 } from "fs/promises";
10999
11070
  import { extname as extname13 } from "path";
11000
- import { parse as parse5 } from "kordoc";
11001
11071
  import { z as z13 } from "zod";
11002
- import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
11003
- import { z as z14 } from "zod";
11004
- import { readFile as readFile14 } from "fs/promises";
11072
+ import { readFile as readFile14, stat as stat4 } from "fs/promises";
11005
11073
  import { extname as extname14 } from "path";
11006
- import { parse as parse6 } from "kordoc";
11074
+ import { z as z14 } from "zod";
11075
+ import { readFile as fsReadFile, stat as stat5 } from "fs/promises";
11007
11076
  import { z as z15 } from "zod";
11008
- import { stat as stat6 } from "fs/promises";
11077
+ import { readFile as readFile15 } from "fs/promises";
11009
11078
  import { extname as extname15 } from "path";
11010
- import { markdownToHwpx } from "kordoc";
11011
11079
  import { z as z16 } from "zod";
11012
- import { stat as stat7 } from "fs/promises";
11080
+ import { stat as stat6 } from "fs/promises";
11013
11081
  import { extname as extname16 } from "path";
11014
- import ExcelJS2 from "exceljs";
11082
+ import { markdownToHwpx } from "kordoc";
11015
11083
  import { z as z17 } from "zod";
11016
- var OLE2_MAGIC = [208, 207, 17, 224, 161, 177, 26, 225];
11017
- var ZIP_MAGIC_0 = 80;
11018
- var ZIP_MAGIC_1 = 75;
11084
+ import { stat as stat7 } from "fs/promises";
11085
+ import { extname as extname17 } from "path";
11086
+ import ExcelJS2 from "exceljs";
11087
+ import { z as z18 } from "zod";
11088
+ function headerBuffer(bytes, n = 64) {
11089
+ return new Uint8Array(bytes.subarray(0, Math.min(n, bytes.length))).buffer;
11090
+ }
11019
11091
  function isOle2Binary(bytes) {
11020
- if (bytes.length < OLE2_MAGIC.length) return false;
11021
- return OLE2_MAGIC.every((b, i) => bytes[i] === b);
11092
+ if (bytes.length < 8) return false;
11093
+ return isOldHwpFile(headerBuffer(bytes));
11094
+ }
11095
+ function isZipBinary(bytes) {
11096
+ if (bytes.length < 4) return false;
11097
+ return isZipFile(headerBuffer(bytes));
11022
11098
  }
11023
11099
  function hwpStructuralGuard(ext, bytes) {
11024
11100
  const isHwpExt = ext === ".hwp";
11025
11101
  const isOle2 = isOle2Binary(bytes);
11026
11102
  if (!isHwpExt && !isOle2) return null;
11027
- if (!isHwpExt && bytes[0] === ZIP_MAGIC_0 && bytes[1] === ZIP_MAGIC_1) return null;
11103
+ if (!isHwpExt && isZipBinary(bytes)) return null;
11028
11104
  return "\uC774 \uC791\uC5C5(\uD45C\xB7\uC140\xB7\uC591\uC2DD\xB7\uCC3E\uAE30\uBC14\uAFB8\uAE30 \uB4F1 \uAD6C\uC870 \uD3B8\uC9D1)\uC740 `.hwpx` \uBB38\uC11C\uC5D0\uC11C \uC9C0\uC6D0\uB429\uB2C8\uB2E4. `.hwp`\uB294 OLE \uBC14\uC774\uB108\uB9AC\uB77C \uAD6C\uC870\uB97C \uBB34\uC190\uC2E4\uB85C \uD328\uCE58\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD574\uACB0: (1) \uBCF8\uBB38 \uD14D\uC2A4\uD2B8\uB9CC \uACE0\uCE58\uB824\uBA74 `propose_edit`\uC744 \uC0AC\uC6A9\uD558\uC138\uC694(.hwp \uC81C\uC790\uB9AC \uD3B8\uC9D1). (2) \uD45C\xB7\uC140\xB7\uC591\uC2DD \uD3B8\uC9D1\uC774 \uD544\uC694\uD558\uBA74 \uD55C\uAE00\uC5D0\uC11C '\uB2E4\uB978 \uC774\uB984\uC73C\uB85C \uC800\uC7A5 \u2192 HWPX(.hwpx)'\uB85C \uBCC0\uD658\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
11029
11105
  }
11030
11106
  async function resolveSafePath(cwd, p) {
@@ -11526,577 +11602,272 @@ var compareDocumentsTool = {
11526
11602
  return output;
11527
11603
  }
11528
11604
  };
11529
- var cellEditItemSchema = z32.object({
11530
- tableIndex: z32.number().int().nonnegative().optional().describe(
11531
- "\uC88C\uD45C \uBAA8\uB4DC: 0-based \uD45C \uC778\uB371\uC2A4(read_document\uC758 kordoc \uBE14\uB85D \uC21C\uC11C, \uC911\uCCA9\uD45C \uC81C\uC678). \uB808\uC774\uBE14 \uBAA8\uB4DC\uC5D0\uC120 \uD0D0\uC0C9 \uBC94\uC704 \uC81C\uD55C\uC6A9(\uC120\uD0DD)."
11532
- ),
11533
- row: z32.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
11534
- col: z32.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
11535
- label: z32.string().optional().describe(
11536
- "\uB808\uC774\uBE14 \uBAA8\uB4DC: \uAE30\uC900 \uC140 \uD14D\uC2A4\uD2B8(\uD2B8\uB9BC \uBE44\uAD50). \uC774 \uC140\uC758 direction \uBC29\uD5A5 \uC778\uC811 \uC140\uC5D0 newText\uB97C \uAE30\uB85D. \uC88C\uD45C \uBAA8\uB4DC\uBA74 \uC0DD\uB7B5."
11537
- ),
11538
- direction: z32.enum(["right", "below"]).optional().describe("\uB808\uC774\uBE14 \uBAA8\uB4DC \uBC29\uD5A5. \uAE30\uBCF8 right(\uC624\uB978\uCABD \uC140), below(\uC544\uB798 \uC140). \uBCD1\uD569 span \uACE0\uB824."),
11539
- newText: z32.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
11540
- expectedText: z32.string().optional().describe(
11541
- "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8(\uC548\uC804 \uAC80\uC99D\uC6A9). \uBD88\uC77C\uCE58 \uC2DC \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815 \uBC29\uC9C0\uB97C \uC704\uD574 \uAD8C\uC7A5."
11542
- )
11543
- }).describe(
11544
- "\uD3B8\uC9D1 \uD56D\uBAA9. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label[+direction]) \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uB458 \uB2E4 \uC9C0\uC815\uD558\uAC70\uB098 \uB458 \uB2E4 \uC0DD\uB7B5\uD558\uBA74 \uC624\uB958\uC785\uB2C8\uB2E4."
11605
+ var SHAPE_ALT_KEYWORDS = [
11606
+ "\uC0AC\uAC01\uD615",
11607
+ "\uC9C1\uC0AC\uAC01\uD615",
11608
+ "\uC815\uC0AC\uAC01\uD615",
11609
+ "\uC6D0",
11610
+ "\uD0C0\uC6D0",
11611
+ "\uC0BC\uAC01\uD615",
11612
+ "\uC774\uB4F1\uBCC0 \uC0BC\uAC01\uD615",
11613
+ "\uC9C1\uAC01 \uC0BC\uAC01\uD615",
11614
+ "\uC120",
11615
+ "\uC9C1\uC120",
11616
+ "\uACE1\uC120",
11617
+ "\uD654\uC0B4\uD45C",
11618
+ "\uAD75\uC740 \uD654\uC0B4\uD45C",
11619
+ "\uC774\uC911 \uD654\uC0B4\uD45C",
11620
+ "\uC624\uAC01\uD615",
11621
+ "\uC721\uAC01\uD615",
11622
+ "\uD314\uAC01\uD615",
11623
+ "\uBCC4",
11624
+ "[4-8]\uC810\uBCC4",
11625
+ "\uC2ED\uC790",
11626
+ "\uC2ED\uC790\uD615",
11627
+ "\uAD6C\uB984",
11628
+ "\uAD6C\uB984\uD615",
11629
+ "\uB9C8\uB984\uBAA8",
11630
+ "\uB3C4\uB11B",
11631
+ "\uD3C9\uD589\uC0AC\uBCC0\uD615",
11632
+ "\uC0AC\uB2E4\uB9AC\uAF34",
11633
+ "\uBD80\uCC44\uAF34",
11634
+ "\uD638",
11635
+ "\uBC18\uC6D0",
11636
+ "\uBB3C\uACB0",
11637
+ "\uBC88\uAC1C",
11638
+ "\uD558\uD2B8",
11639
+ "\uBE57\uAE08",
11640
+ "\uBE14\uB85D \uD654\uC0B4\uD45C",
11641
+ "\uC218\uC2DD",
11642
+ "\uD45C",
11643
+ "\uADF8\uB9BC",
11644
+ "\uAC1C\uCCB4",
11645
+ "\uADF8\uB9AC\uAE30\\s?\uAC1C\uCCB4",
11646
+ "\uBB36\uC74C\\s?\uAC1C\uCCB4",
11647
+ "\uAE00\uC0C1\uC790",
11648
+ "\uC218\uC2DD\\s?\uAC1C\uCCB4",
11649
+ "OLE\\s?\uAC1C\uCCB4"
11650
+ ].join("|");
11651
+ var BUGGY_SHAPE_STRIP = new RegExp(
11652
+ `(?:\uBAA8\uC11C\uB9AC\uAC00 \uB465\uADFC |\uB465\uADFC )?(?:${SHAPE_ALT_KEYWORDS})\\s?\uC785\uB2C8\uB2E4\\.?`,
11653
+ "g"
11545
11654
  );
11546
- var proposeCellEditSchema = z32.object({
11547
- path: z32.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
11548
- edits: z32.array(cellEditItemSchema).min(1).describe(
11549
- "\uD3B8\uC9D1 \uBAA9\uB85D. \uAC01 \uD56D\uBAA9\uC740 \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label+direction) \uC911 \uD558\uB098"
11550
- ),
11551
- summary: z32.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
11552
- });
11553
- function tokenizeHwpxXml(xml) {
11554
- const tokens = [];
11555
- const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*/g;
11556
- let startPos = 0;
11557
- let m = re.exec(xml);
11558
- while (m !== null) {
11559
- const raw = m[0];
11560
- const pos = m.index;
11561
- if (raw.startsWith("<hp:tbl")) {
11562
- tokens.push({ kind: "tbl_open", pos, end: pos + raw.length });
11563
- } else if (raw === "</hp:tbl>") {
11564
- tokens.push({ kind: "tbl_close", pos, end: pos + raw.length });
11565
- } else if (raw.startsWith("<hp:tc")) {
11566
- tokens.push({ kind: "tc_open", pos, end: pos + raw.length });
11567
- } else if (raw === "</hp:tc>") {
11568
- tokens.push({ kind: "tc_close", pos, end: pos + raw.length });
11569
- } else if (raw === "<hp:t/>") {
11570
- tokens.push({ kind: "t_empty", pos, end: pos + raw.length });
11571
- } else if (raw === "<hp:t>") {
11572
- tokens.push({ kind: "t_open", pos, end: pos + raw.length });
11573
- } else if (raw.startsWith("<hp:cellAddr")) {
11574
- const colM = raw.match(/colAddr="(\d+)"/);
11575
- const rowM = raw.match(/rowAddr="(\d+)"/);
11576
- if (colM && rowM) {
11577
- const selfClose = xml.indexOf("/>", pos);
11578
- const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
11579
- tokens.push({
11580
- kind: "cell_addr",
11581
- pos,
11582
- end,
11583
- colAddr: Number(colM[1]),
11584
- rowAddr: Number(rowM[1])
11585
- });
11586
- }
11587
- } else if (raw.startsWith("<hp:cellSpan")) {
11588
- const colM = raw.match(/colSpan="(\d+)"/);
11589
- const rowM = raw.match(/rowSpan="(\d+)"/);
11590
- const selfClose = xml.indexOf("/>", pos);
11591
- const end = selfClose >= 0 ? selfClose + 2 : pos + raw.length;
11592
- tokens.push({
11593
- kind: "cell_span",
11594
- pos,
11595
- end,
11596
- colSpan: colM ? Number(colM[1]) : 1,
11597
- rowSpan: rowM ? Number(rowM[1]) : 1
11598
- });
11599
- }
11600
- startPos = re.lastIndex;
11601
- m = re.exec(xml);
11602
- }
11603
- void startPos;
11604
- return tokens;
11655
+ var FIXED_SHAPE_STRIP = new RegExp(
11656
+ `(?<![\uAC00-\uD7A3])(?:\uBAA8\uC11C\uB9AC\uAC00 \uB465\uADFC |\uB465\uADFC )?(?:${SHAPE_ALT_KEYWORDS})\\s?\uC785\uB2C8\uB2E4\\.?`,
11657
+ "g"
11658
+ );
11659
+ function applyStrip(raw, re) {
11660
+ const normalized = raw.replace(/[ \t]+/g, " ").trim();
11661
+ return normalized.replace(re, "").trim();
11605
11662
  }
11606
- function escapeXml(text3) {
11607
- return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
11663
+ function decodeXmlEntities(s) {
11664
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(Number.parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number.parseInt(d, 10))).replace(/&amp;/g, "&");
11608
11665
  }
11609
- function collectDirectTcRanges(tokens, tblStart, tblEnd) {
11610
- const tblTokens = tokens.filter((t) => t.pos > tblStart && t.pos < tblEnd);
11611
- const tcRanges = [];
11612
- const tcStack = [];
11613
- let innerDepth = 0;
11614
- for (const tok of tblTokens) {
11615
- if (tok.kind === "tbl_open") {
11616
- innerDepth++;
11617
- } else if (tok.kind === "tbl_close") {
11618
- innerDepth--;
11619
- } else if (tok.kind === "tc_open") {
11620
- tcStack.push({ pos: tok.pos, depth: innerDepth });
11621
- } else if (tok.kind === "tc_close") {
11622
- const entry = tcStack.pop();
11623
- if (entry !== void 0) {
11624
- tcRanges.push({ start: entry.pos, end: tok.end, depth: entry.depth });
11666
+ async function extractHwpxParagraphTexts(bytes) {
11667
+ const zip = await import_jszip.default.loadAsync(bytes);
11668
+ const out = [];
11669
+ for (const name of Object.keys(zip.files)) {
11670
+ if (!/^Contents\/section\d*\.xml$/i.test(name)) continue;
11671
+ const entry = zip.files[name];
11672
+ if (!entry) continue;
11673
+ const xml = await entry.async("string");
11674
+ for (const seg of xml.split(/(?=<hp:p\b)/)) {
11675
+ let para = "";
11676
+ for (const m of seg.matchAll(/<hp:t(?:\s[^>]*)?>([\s\S]*?)<\/hp:t>/g)) {
11677
+ para += decodeXmlEntities((m[1] ?? "").replace(/<[^>]+>/g, ""));
11625
11678
  }
11679
+ if (para.trim()) out.push(para);
11626
11680
  }
11627
11681
  }
11628
- return tcRanges.filter((tc) => tc.depth === 0);
11682
+ return out;
11629
11683
  }
11630
- function readOwnTextFromTc(xml, tokens, tcStart, tcEnd) {
11631
- const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
11632
- let innerDepth = 0;
11633
- let text3 = "";
11634
- for (const t of tcTokens) {
11635
- if (t.kind === "tbl_open") innerDepth++;
11636
- else if (t.kind === "tbl_close") innerDepth--;
11637
- else if (t.kind === "t_empty" && innerDepth === 0) {
11638
- } else if (t.kind === "t_open" && innerDepth === 0) {
11639
- const closePos = xml.indexOf("</hp:t>", t.end);
11640
- if (closePos >= 0) {
11641
- text3 += xml.substring(t.end, closePos);
11642
- }
11643
- }
11644
- }
11645
- return text3.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
11684
+ function splitDecoration(line) {
11685
+ const m = line.match(/^(\s*(?:#{1,6}\s+|[-*+]\s+|\d+\.\s+|>\s+)?)([\s\S]*)$/);
11686
+ if (!m) return { prefix: "", content: line };
11687
+ return { prefix: m[1] ?? "", content: m[2] ?? "" };
11646
11688
  }
11647
- function findTopLevelTableRange(tokens, tableIndex) {
11648
- let topLevelCount = 0;
11649
- let depth = 0;
11650
- let targetStart = -1;
11651
- for (const tok of tokens) {
11652
- if (tok.kind === "tbl_open") {
11653
- if (depth === 0) {
11654
- if (topLevelCount === tableIndex) {
11655
- targetStart = tok.pos;
11656
- }
11657
- topLevelCount++;
11658
- }
11659
- depth++;
11660
- } else if (tok.kind === "tbl_close") {
11661
- depth--;
11662
- if (depth === 0 && topLevelCount === tableIndex + 1 && targetStart >= 0) {
11663
- return { start: targetStart, end: tok.end };
11689
+ function restoreOverStrippedShapeText(markdown, paragraphTexts) {
11690
+ const lines = markdown.split("\n");
11691
+ const used = /* @__PURE__ */ new Set();
11692
+ let changed = false;
11693
+ for (const raw of paragraphTexts) {
11694
+ const buggy = applyStrip(raw, BUGGY_SHAPE_STRIP);
11695
+ const fixed = applyStrip(raw, FIXED_SHAPE_STRIP);
11696
+ if (buggy === fixed) continue;
11697
+ if (!fixed) continue;
11698
+ if (!buggy) continue;
11699
+ for (let i = 0; i < lines.length; i++) {
11700
+ if (used.has(i)) continue;
11701
+ const { prefix, content } = splitDecoration(lines[i] ?? "");
11702
+ if (content.trim() === buggy && content.trim() !== fixed) {
11703
+ lines[i] = prefix + fixed;
11704
+ used.add(i);
11705
+ changed = true;
11706
+ break;
11664
11707
  }
11665
11708
  }
11666
11709
  }
11667
- return null;
11710
+ return changed ? lines.join("\n") : markdown;
11668
11711
  }
11669
- function readCellAddrSpan(tokens, tcStart, tcEnd) {
11670
- const tcTokens = tokens.filter((t) => t.pos >= tcStart && t.pos < tcEnd);
11671
- let addr = null;
11672
- let span = { colSpan: 1, rowSpan: 1 };
11673
- for (const t of tcTokens) {
11674
- if (t.kind === "cell_addr" && t.colAddr !== void 0 && t.rowAddr !== void 0) {
11675
- addr = { colAddr: t.colAddr, rowAddr: t.rowAddr };
11676
- } else if (t.kind === "cell_span" && t.colSpan !== void 0 && t.rowSpan !== void 0) {
11677
- span = { colSpan: t.colSpan, rowSpan: t.rowSpan };
11678
- }
11679
- }
11680
- if (!addr) return null;
11681
- return { ...addr, ...span };
11682
- }
11683
- function applyCellEditsToSectionXml(xml, edits) {
11684
- const tokens = tokenizeHwpxXml(xml);
11685
- const results = edits.map(() => ({ success: false }));
11686
- let totalTopLevelTbls = 0;
11687
- {
11688
- let d = 0;
11689
- for (const tok of tokens) {
11690
- if (tok.kind === "tbl_open") {
11691
- if (d === 0) totalTopLevelTbls++;
11692
- d++;
11693
- } else if (tok.kind === "tbl_close") {
11694
- d--;
11695
- }
11696
- }
11697
- }
11698
- const replacements = [];
11699
- for (let ei = 0; ei < edits.length; ei++) {
11700
- const edit = edits[ei];
11701
- const tblRange = findTopLevelTableRange(tokens, edit.tableIndex);
11702
- if (!tblRange) {
11703
- results[ei] = {
11704
- success: false,
11705
- error: `\uD45C ${edit.tableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 \uC139\uC158\uC5D0 \uCD1D ${totalTopLevelTbls}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
11706
- };
11707
- continue;
11708
- }
11709
- const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
11710
- const tblTokens = tokens.filter((t) => t.pos >= tblRange.start && t.pos < tblRange.end);
11711
- let found = false;
11712
- for (const tc of directTcs) {
11713
- const tcTokens = tblTokens.filter((t) => t.pos >= tc.start && t.pos < tc.end);
11714
- const hasAddr = tcTokens.some(
11715
- (t) => t.kind === "cell_addr" && t.colAddr === edit.col && t.rowAddr === edit.row
11716
- );
11717
- if (!hasAddr) continue;
11718
- let innerDepth = 0;
11719
- const ownTRuns = [];
11720
- for (const t of tokens.filter((x) => x.pos >= tc.start && x.pos < tc.end)) {
11721
- if (t.kind === "tbl_open") innerDepth++;
11722
- else if (t.kind === "tbl_close") innerDepth--;
11723
- else if (t.kind === "t_empty" && innerDepth === 0) {
11724
- ownTRuns.push({ isEmpty: true, tagPos: t.pos, tagEnd: t.end });
11725
- } else if (t.kind === "t_open" && innerDepth === 0) {
11726
- const closePos = xml.indexOf("</hp:t>", t.end);
11727
- if (closePos >= 0) {
11728
- ownTRuns.push({ isEmpty: false, openEnd: t.end, closePos });
11712
+ function collectBlockTextSlots(blocks) {
11713
+ const slots = [];
11714
+ const walk = (bs) => {
11715
+ for (const b of bs) {
11716
+ if (typeof b.text === "string") {
11717
+ slots.push({
11718
+ get: () => b.text ?? "",
11719
+ set: (v) => {
11720
+ b.text = v;
11729
11721
  }
11730
- }
11731
- }
11732
- const currentText = ownTRuns.map((r) => r.isEmpty ? "" : xml.substring(r.openEnd, r.closePos)).join("").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
11733
- if (edit.expectedText !== void 0 && edit.expectedText !== currentText) {
11734
- results[ei] = {
11735
- success: false,
11736
- oldText: currentText,
11737
- error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC758 \uD604\uC7AC \uD14D\uC2A4\uD2B8\uAC00 \uC608\uC0C1\uAC12\uACFC \uB2E4\uB985\uB2C8\uB2E4. \uC608\uC0C1: "${edit.expectedText}", \uC2E4\uC81C: "${currentText}". \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`
11738
- };
11739
- found = true;
11740
- break;
11741
- }
11742
- if (ownTRuns.length === 0) {
11743
- results[ei] = {
11744
- success: false,
11745
- oldText: currentText,
11746
- error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC5D0 \uD14D\uC2A4\uD2B8 \uB7F0\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. <hp:t> \uB610\uB294 <hp:t/> \uB7F0\uC774 \uBC1C\uACAC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC140 \uAD6C\uC870\uB97C \uD655\uC778\uD558\uC138\uC694.`
11747
- };
11748
- found = true;
11749
- break;
11750
- }
11751
- const escapedNew = escapeXml(edit.newText);
11752
- const patches = [];
11753
- const firstRun = ownTRuns[0];
11754
- if (firstRun.isEmpty) {
11755
- patches.push({
11756
- from: firstRun.tagPos,
11757
- to: firstRun.tagEnd,
11758
- text: `<hp:t>${escapedNew}</hp:t>`
11759
11722
  });
11760
- } else {
11761
- patches.push({ from: firstRun.openEnd, to: firstRun.closePos, text: escapedNew });
11762
11723
  }
11763
- for (let ri = 1; ri < ownTRuns.length; ri++) {
11764
- const run = ownTRuns[ri];
11765
- if (!run.isEmpty) {
11766
- patches.push({ from: run.openEnd, to: run.closePos, text: "" });
11724
+ if (b.table) {
11725
+ for (const row of b.table.cells) {
11726
+ for (const cell of row) {
11727
+ slots.push({
11728
+ get: () => cell.text,
11729
+ set: (v) => {
11730
+ cell.text = v;
11731
+ }
11732
+ });
11733
+ if (cell.blocks) walk(cell.blocks);
11734
+ }
11767
11735
  }
11768
11736
  }
11769
- replacements.push({ editIdx: ei, patches });
11770
- results[ei] = { success: true, oldText: currentText };
11771
- found = true;
11772
- break;
11737
+ if (b.children) walk(b.children);
11773
11738
  }
11774
- if (!found) {
11775
- results[ei] = {
11776
- success: false,
11777
- error: `\uD45C ${edit.tableIndex}\uC5D0\uC11C \uC140 (\uD589 ${edit.row}, \uC5F4 ${edit.col})\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. cellAddr colAddr="${edit.col}" rowAddr="${edit.row}"\uC5D0 \uD574\uB2F9\uD558\uB294 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
11778
- };
11739
+ };
11740
+ walk(blocks);
11741
+ return slots;
11742
+ }
11743
+ function restoreOverStrippedBlocks(blocks, paragraphTexts) {
11744
+ const slots = collectBlockTextSlots(blocks);
11745
+ const used = /* @__PURE__ */ new Set();
11746
+ let changed = false;
11747
+ for (const raw of paragraphTexts) {
11748
+ const buggy = applyStrip(raw, BUGGY_SHAPE_STRIP);
11749
+ const fixed = applyStrip(raw, FIXED_SHAPE_STRIP);
11750
+ if (buggy === fixed) continue;
11751
+ if (!fixed || !buggy) continue;
11752
+ for (let i = 0; i < slots.length; i++) {
11753
+ if (used.has(i)) continue;
11754
+ const cur = slots[i]?.get().trim() ?? "";
11755
+ if (cur === buggy && cur !== fixed) {
11756
+ slots[i]?.set(fixed);
11757
+ used.add(i);
11758
+ changed = true;
11759
+ break;
11760
+ }
11779
11761
  }
11780
11762
  }
11781
- if (results.some((r) => !r.success)) {
11782
- return { newXml: xml, results };
11783
- }
11784
- const allPatches = replacements.flatMap((r) => r.patches).sort((a, b) => b.from - a.from);
11785
- let result = xml;
11786
- for (const patch of allPatches) {
11787
- result = result.substring(0, patch.from) + patch.text + result.substring(patch.to);
11788
- }
11789
- return { newXml: result, results };
11763
+ return changed;
11790
11764
  }
11791
- async function applyEditsToHwpx(hwpxBuffer, edits) {
11792
- const zip = await import_jszip2.default.loadAsync(hwpxBuffer);
11793
- const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11794
- const sectionXmls = [];
11795
- const sectionTblCounts = [];
11796
- for (const sf of sectionFiles) {
11797
- const entry = zip.file(sf);
11798
- const xml = entry ? await entry.async("string") : "";
11799
- sectionXmls.push(xml);
11800
- const tokens = tokenizeHwpxXml(xml);
11801
- let count = 0;
11802
- let depth = 0;
11803
- for (const tok of tokens) {
11804
- if (tok.kind === "tbl_open") {
11805
- if (depth === 0) count++;
11806
- depth++;
11807
- } else if (tok.kind === "tbl_close") {
11808
- depth--;
11809
- }
11765
+ function isZip(bytes) {
11766
+ if (bytes.length < 4) return false;
11767
+ return isZipFile2(new Uint8Array(bytes.subarray(0, 4)).buffer);
11768
+ }
11769
+ async function resolveHwpxBytes(input) {
11770
+ try {
11771
+ if (typeof input === "string") {
11772
+ if (!/\.hwpx$/i.test(input)) return null;
11773
+ return new Uint8Array(await readFile4(input));
11810
11774
  }
11811
- sectionTblCounts.push(count);
11775
+ const u8 = input instanceof Uint8Array ? input : new Uint8Array(input instanceof ArrayBuffer ? input : input);
11776
+ return isZip(u8) ? u8 : null;
11777
+ } catch {
11778
+ return null;
11812
11779
  }
11813
- const sectionEdits = sectionFiles.map(() => []);
11814
- let offset = 0;
11815
- for (let si = 0; si < sectionFiles.length; si++) {
11816
- const count = sectionTblCounts[si] ?? 0;
11817
- for (let ei = 0; ei < edits.length; ei++) {
11818
- const edit = edits[ei];
11819
- if (edit.tableIndex >= offset && edit.tableIndex < offset + count) {
11820
- const secEdits = sectionEdits[si];
11821
- if (secEdits) {
11822
- secEdits.push({
11823
- tableIndex: edit.tableIndex - offset,
11824
- // 섹션 상대 인덱스
11825
- row: edit.row,
11826
- col: edit.col,
11827
- newText: edit.newText,
11828
- expectedText: edit.expectedText,
11829
- originalEditIdx: ei
11830
- });
11831
- }
11832
- }
11780
+ }
11781
+ async function parse(input, options) {
11782
+ const result = await kordocParse(input, options);
11783
+ if (!result.success || typeof result.markdown !== "string" || !result.markdown) {
11784
+ return result;
11785
+ }
11786
+ const bytes = await resolveHwpxBytes(input);
11787
+ if (!bytes) return result;
11788
+ try {
11789
+ const paragraphTexts = await extractHwpxParagraphTexts(bytes);
11790
+ if (paragraphTexts.length === 0) return result;
11791
+ const repaired = restoreOverStrippedShapeText(result.markdown, paragraphTexts);
11792
+ if (Array.isArray(result.blocks)) {
11793
+ restoreOverStrippedBlocks(result.blocks, paragraphTexts);
11794
+ }
11795
+ if (repaired !== result.markdown) {
11796
+ return { ...result, markdown: repaired };
11833
11797
  }
11834
- offset += count;
11798
+ } catch {
11835
11799
  }
11836
- const totalTables = sectionTblCounts.reduce((a, b) => a + b, 0);
11837
- const allResults = edits.map((edit, ei) => ({
11838
- success: false,
11839
- error: `\uD45C ${edit?.tableIndex ?? ei}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uC5D0 \uCD1D ${totalTables}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
11840
- }));
11841
- const newSectionXmls = [...sectionXmls];
11842
- for (let si = 0; si < sectionFiles.length; si++) {
11843
- const sEdits = sectionEdits[si] ?? [];
11844
- if (sEdits.length === 0) continue;
11845
- const srcXml = sectionXmls[si] ?? "";
11846
- const { newXml, results } = applyCellEditsToSectionXml(srcXml, sEdits);
11847
- newSectionXmls[si] = newXml;
11848
- for (let i = 0; i < sEdits.length; i++) {
11849
- const sEdit = sEdits[i];
11850
- const res = results[i];
11851
- if (sEdit && res) {
11852
- allResults[sEdit.originalEditIdx] = res;
11853
- }
11854
- }
11855
- }
11856
- if (allResults.some((r) => !r.success)) {
11857
- return { buffer: hwpxBuffer, results: allResults };
11858
- }
11859
- const out = new import_jszip2.default();
11860
- const mimetypeEntry = zip.file("mimetype");
11861
- if (mimetypeEntry) {
11862
- out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
11863
- }
11864
- for (const [name, entry] of Object.entries(zip.files)) {
11865
- if (name === "mimetype" || entry.dir) continue;
11866
- const sectionIdx = sectionFiles.indexOf(name);
11867
- if (sectionIdx >= 0) {
11868
- out.file(name, newSectionXmls[sectionIdx] ?? "");
11869
- } else {
11870
- out.file(name, await entry.async("uint8array"));
11871
- }
11872
- }
11873
- const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
11874
- return { buffer: new Uint8Array(buf), results: allResults };
11800
+ return result;
11875
11801
  }
11876
- var proposeCellEditTool = {
11877
- name: "propose_cell_edit",
11878
- description: "HWPX \uBB38\uC11C\uC758 \uD45C \uC140 \uB0B4\uC6A9\uC744 XML \uC9C1\uC811 \uD328\uCE58 \uBC29\uC2DD\uC73C\uB85C \uC218\uC815\uD569\uB2C8\uB2E4. \uBCD1\uD569 \uC140(cellSpan/rowSpan)\uC774 \uC788\uB294 \uD45C\uC5D0\uC11C\uB3C4 \uBCD1\uD569 \uAD6C\uC870\uB97C \uC644\uC804\uD788 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uBE48 \uC140(<hp:t/> self-closing \uB7F0)\uB3C4 \uCC44\uC6B8 \uC218 \uC788\uC5B4 \uC591\uC2DD(form) \uD3B8\uC9D1\uC5D0 \uC801\uD569\uD569\uB2C8\uB2E4. \uC140 \uC8FC\uC18C \uC9C0\uC815 \uBC29\uBC95: (1) \uC88C\uD45C \uBAA8\uB4DC \u2014 tableIndex + row + col \uC9C1\uC811 \uC9C0\uC815. (2) \uB808\uC774\uBE14 \uBAA8\uB4DC \u2014 label(\uC778\uC811 \uB808\uC774\uBE14 \uC140 \uD14D\uC2A4\uD2B8) + direction(right/below, \uAE30\uBCF8 right) + \uC120\uD0DD\uC801 tableIndex\uB85C \uB808\uC774\uBE14 \uC606/\uC544\uB798 \uC140\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uD3B8\uC9D1. \uBCD1\uD569 \uB808\uC774\uBE14 \uC140\uB3C4 colSpan/rowSpan\uC744 \uBC18\uC601\uD558\uC5EC \uB300\uC0C1 \uC140\uC744 \uACC4\uC0B0\uD569\uB2C8\uB2E4. propose_edit/propose_form_fill\uC740 \uB9C8\uD06C\uB2E4\uC6B4 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD\uC73C\uB85C \uBCD1\uD569 \uC140\uC744 \uC18C\uC2E4\uC2DC\uD0A4\uBBC0\uB85C, \uBCD1\uD569 \uC140\uC774 \uC788\uB294 \uD45C\uB97C \uC218\uC815\uD560 \uB54C\uB294 \uC774 \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4. .hwp\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA70 Hancom\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. \uC218\uC815 \uC804\uC5D0 \uBC18\uB4DC\uC2DC read_document\uB85C \uC6D0\uBCF8\uC744 \uC77D\uACE0, expectedText\uB97C \uC9C0\uC815\uD558\uC5EC \uC548\uC804\uD558\uAC8C \uC218\uC815\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 diff \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
11879
- inputSchema: proposeCellEditSchema,
11802
+ var MAX_PREVIEW_CHARS = 4e3;
11803
+ var exportDocumentSchema = z32.object({
11804
+ path: z32.string().describe("\uBCC0\uD658\uD560 \uC6D0\uBCF8 \uBB38\uC11C \uACBD\uB85C (.hwp/.hwpx/.docx \uB4F1 \u2014 cwd \uAE30\uC900 \uC0C1\uB300/\uC808\uB300 \uACBD\uB85C)"),
11805
+ outputPath: z32.string().describe("\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C \u2014 \uD655\uC7A5\uC790\uB85C \uD615\uC2DD \uACB0\uC815(.html/.htm \uB610\uB294 .pdf)"),
11806
+ summary: z32.string().optional().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
11807
+ });
11808
+ var exportDocumentTool = {
11809
+ name: "export_document",
11810
+ description: "\uBB38\uC11C(.hwp/.hwpx/.docx \uB4F1)\uB97C HTML \uB610\uB294 PDF\uB85C \uB0B4\uBCF4\uB0C5\uB2C8\uB2E4. \uCD9C\uB825 \uACBD\uB85C \uD655\uC7A5\uC790\uB85C \uD615\uC2DD\uC744 \uACB0\uC815\uD569\uB2C8\uB2E4(.html/.htm \uB610\uB294 .pdf). \uC6D0\uBCF8\uC740 \uBCC0\uACBD\uD558\uC9C0 \uC54A\uACE0 \uC0C8 \uD30C\uC77C\uC744 \uB9CC\uB4ED\uB2C8\uB2E4. HTML\uC740 \uD56D\uC0C1 \uAC00\uB2A5\uD558\uBA70, PDF\uB294 puppeteer-core\uAC00 \uC124\uCE58\uB41C \uD658\uACBD\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4(\uBBF8\uC124\uCE58 \uC2DC \uC548\uB0B4). \uBCC0\uACBD \uC0AC\uD56D\uC740 \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
11811
+ inputSchema: exportDocumentSchema,
11880
11812
  requiresApproval: true,
11881
11813
  propose: async ({
11882
11814
  input,
11883
11815
  ctx
11884
11816
  }) => {
11885
11817
  const safePath = await resolveSafePath(ctx.cwd, input.path);
11886
- const ext = extname3(safePath).toLowerCase();
11887
- if (ext !== ".hwpx" && ext !== ".hwp") {
11888
- return `\uC624\uB958: propose_cell_edit\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. \uD45C \uC140 \uC9C1\uC811 \uD3B8\uC9D1\uC740 .hwpx \uD3EC\uB9F7\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.`;
11889
- }
11890
- let originalBuffer;
11891
- try {
11892
- originalBuffer = await readFile4(safePath);
11893
- } catch {
11894
- return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uAC70\uB098 read_document\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
11895
- }
11896
- const originalBytes = new Uint8Array(originalBuffer.buffer);
11897
- const structuralGuard = hwpStructuralGuard(ext, originalBytes);
11898
- if (structuralGuard !== null) {
11899
- return structuralGuard;
11818
+ const outPath = await resolveSafePath(ctx.cwd, input.outputPath);
11819
+ const outExt = extname3(outPath).toLowerCase();
11820
+ const format = outExt === ".html" || outExt === ".htm" ? "html" : outExt === ".pdf" ? "pdf" : null;
11821
+ if (format === null) {
11822
+ return `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uCD9C\uB825 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${outExt || "(\uD655\uC7A5\uC790 \uC5C6\uC74C)"}. \uCD9C\uB825 \uACBD\uB85C\uB97C .html \uB610\uB294 .pdf \uB85C \uC9C0\uC815\uD558\uC138\uC694.`;
11900
11823
  }
11901
- if (originalBuffer[0] !== 80 || originalBuffer[1] !== 75) {
11902
- return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
11903
- }
11904
- const zipForLabel = await import_jszip2.default.loadAsync(new Uint8Array(originalBuffer.buffer));
11905
- const sectionFilesForLabel = Object.keys(zipForLabel.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
11906
- const sectionInfos = [];
11907
- let globalTblOffset = 0;
11908
- for (const sf of sectionFilesForLabel) {
11909
- const entry = zipForLabel.file(sf);
11910
- const xml = entry ? await entry.async("string") : "";
11911
- const tokens = tokenizeHwpxXml(xml);
11912
- let count = 0;
11913
- let d = 0;
11914
- for (const tok of tokens) {
11915
- if (tok.kind === "tbl_open") {
11916
- if (d === 0) count++;
11917
- d++;
11918
- } else if (tok.kind === "tbl_close") {
11919
- d--;
11920
- }
11921
- }
11922
- sectionInfos.push({ xml, tblCount: count, globalOffset: globalTblOffset });
11923
- globalTblOffset += count;
11924
- }
11925
- function resolveLabelAcrossSections(label, direction, scopedTableIndex) {
11926
- const trimmedLabel = label.trim();
11927
- const allMatches = [];
11928
- for (const si of sectionInfos) {
11929
- const tokens = tokenizeHwpxXml(si.xml);
11930
- let localStart = 0;
11931
- let localEnd = si.tblCount - 1;
11932
- if (scopedTableIndex !== void 0) {
11933
- const localIdx2 = scopedTableIndex - si.globalOffset;
11934
- if (localIdx2 < 0 || localIdx2 >= si.tblCount) continue;
11935
- localStart = localIdx2;
11936
- localEnd = localIdx2;
11937
- }
11938
- for (let li = localStart; li <= localEnd; li++) {
11939
- const tblRange = findTopLevelTableRange(tokens, li);
11940
- if (!tblRange) continue;
11941
- const directTcs = collectDirectTcRanges(tokens, tblRange.start, tblRange.end);
11942
- for (const tc of directTcs) {
11943
- const cellText = readOwnTextFromTc(si.xml, tokens, tc.start, tc.end).trim();
11944
- if (cellText === trimmedLabel) {
11945
- const addrSpan2 = readCellAddrSpan(tokens, tc.start, tc.end);
11946
- if (addrSpan2) {
11947
- allMatches.push({
11948
- globalTableIndex: si.globalOffset + li,
11949
- addrSpan: addrSpan2,
11950
- sectionXml: si.xml,
11951
- sectionOffset: si.globalOffset
11952
- });
11953
- }
11954
- }
11955
- }
11956
- }
11957
- }
11958
- if (allMatches.length === 0) {
11959
- const scope = scopedTableIndex !== void 0 ? `\uD45C ${scopedTableIndex}` : "\uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uD45C";
11960
- return {
11961
- error: `\uB808\uC774\uBE14 "${label}"\uC744(\uB97C) ${scope}\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.`
11962
- };
11963
- }
11964
- if (allMatches.length > 1) {
11965
- const locs = allMatches.map(
11966
- (m) => `\uD45C ${m.globalTableIndex} (\uD589 ${m.addrSpan.rowAddr}, \uC5F4 ${m.addrSpan.colAddr})`
11967
- ).join(", ");
11968
- return {
11969
- error: `\uB808\uC774\uBE14 "${label}"\uC774(\uAC00) \uC5EC\uB7EC \uC140\uC5D0\uC11C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${locs}. tableIndex\uB85C \uD0D0\uC0C9 \uBC94\uC704\uB97C \uC881\uD788\uAC70\uB098 \uC88C\uD45C(row/col)\uB97C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.`
11970
- };
11971
- }
11972
- const match = allMatches[0];
11973
- const { addrSpan, globalTableIndex, sectionXml, sectionOffset } = match;
11974
- let targetRow;
11975
- let targetCol;
11976
- if (direction === "right") {
11977
- targetRow = addrSpan.rowAddr;
11978
- targetCol = addrSpan.colAddr + addrSpan.colSpan;
11979
- } else {
11980
- targetRow = addrSpan.rowAddr + addrSpan.rowSpan;
11981
- targetCol = addrSpan.colAddr;
11982
- }
11983
- const localIdx = globalTableIndex - sectionOffset;
11984
- const tokens2 = tokenizeHwpxXml(sectionXml);
11985
- const tblRange2 = findTopLevelTableRange(tokens2, localIdx);
11986
- if (!tblRange2) {
11987
- return { error: `\uD45C ${globalTableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958).` };
11988
- }
11989
- const directTcs2 = collectDirectTcRanges(tokens2, tblRange2.start, tblRange2.end);
11990
- const tblTokens2 = tokens2.filter((t) => t.pos >= tblRange2.start && t.pos < tblRange2.end);
11991
- let targetExists = false;
11992
- for (const tc of directTcs2) {
11993
- const tcTokens = tblTokens2.filter((t) => t.pos >= tc.start && t.pos < tc.end);
11994
- if (tcTokens.some(
11995
- (t) => t.kind === "cell_addr" && t.colAddr === targetCol && t.rowAddr === targetRow
11996
- )) {
11997
- targetExists = true;
11998
- break;
11999
- }
12000
- }
12001
- if (!targetExists) {
12002
- const dirLabel = direction === "right" ? "\uC624\uB978\uCABD" : "\uC544\uB798";
12003
- return {
12004
- error: `\uB808\uC774\uBE14 "${label}" (\uD45C ${globalTableIndex}, \uD589 ${addrSpan.rowAddr}, \uC5F4 ${addrSpan.colAddr})\uC758 ${dirLabel} \uC140 (\uD589 ${targetRow}, \uC5F4 ${targetCol})\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. direction \uB610\uB294 \uC88C\uD45C\uB97C \uD655\uC778\uD558\uC138\uC694.`
12005
- };
12006
- }
12007
- return { tableIndex: globalTableIndex, row: targetRow, col: targetCol };
11824
+ const parseResult = await parse(safePath);
11825
+ if (!parseResult.success) {
11826
+ const msg = kordocErrorMessage(
11827
+ parseResult.code,
11828
+ `\uC6D0\uBCF8 \uBB38\uC11C\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${parseResult.error}`
11829
+ );
11830
+ return `\uC624\uB958: ${msg}`;
12008
11831
  }
12009
- const resolvedEdits = [];
12010
- const resolveErrors = [];
12011
- for (let i = 0; i < input.edits.length; i++) {
12012
- const e = input.edits[i];
12013
- if (!e) continue;
12014
- if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
12015
- resolveErrors.push(
12016
- `\uD3B8\uC9D1 #${i + 1}: label\uACFC row/col\uC744 \uB3D9\uC2DC\uC5D0 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.`
12017
- );
12018
- } else if (e.label !== void 0) {
12019
- const direction = e.direction ?? "right";
12020
- const resolved = resolveLabelAcrossSections(e.label, direction, e.tableIndex);
12021
- if ("error" in resolved) {
12022
- resolveErrors.push(`\uD3B8\uC9D1 #${i + 1} (\uB808\uC774\uBE14 "${e.label}"): ${resolved.error}`);
12023
- } else {
12024
- resolvedEdits.push({
12025
- tableIndex: resolved.tableIndex,
12026
- row: resolved.row,
12027
- col: resolved.col,
12028
- newText: e.newText,
12029
- expectedText: e.expectedText,
12030
- label: e.label
12031
- });
11832
+ const markdown = parseResult.markdown;
11833
+ let stagedData;
11834
+ const warnings = [];
11835
+ if (format === "html") {
11836
+ stagedData = new TextEncoder().encode(renderHtml(markdown));
11837
+ } else {
11838
+ try {
11839
+ const pdf = await markdownToPdf(markdown);
11840
+ stagedData = new Uint8Array(pdf);
11841
+ } catch (err) {
11842
+ const m = err instanceof Error ? err.message : String(err);
11843
+ if (/puppeteer/i.test(m)) {
11844
+ return "\uC624\uB958: PDF \uB0B4\uBCF4\uB0B4\uAE30\uC5D0\uB294 puppeteer-core\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4(\uC774 \uD658\uACBD\uC5D0 \uBBF8\uC124\uCE58). \uD574\uACB0: (1) \uCD9C\uB825 \uACBD\uB85C\uB97C .html \uB85C \uC9C0\uC815\uD574 HTML\uB85C \uB0B4\uBCF4\uB0B4\uAC70\uB098, (2) `npm install -g puppeteer-core` \uD6C4 Chrome/Chromium \uC2E4\uD589 \uD30C\uC77C \uACBD\uB85C\uB97C \uC124\uC815\uD558\uC138\uC694.";
12032
11845
  }
12033
- } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
12034
- resolvedEdits.push({
12035
- tableIndex: e.tableIndex,
12036
- row: e.row,
12037
- col: e.col,
12038
- newText: e.newText,
12039
- expectedText: e.expectedText
12040
- });
12041
- } else {
12042
- resolveErrors.push(
12043
- `\uD3B8\uC9D1 #${i + 1}: \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB97C \uC644\uC804\uD788 \uC9C0\uC815\uD558\uC138\uC694. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`
12044
- );
11846
+ return `\uC624\uB958: PDF \uC0DD\uC131\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${m}`;
12045
11847
  }
12046
11848
  }
12047
- if (resolveErrors.length > 0) {
12048
- return `\uC624\uB958: \uB2E4\uC74C \uB808\uC774\uBE14\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12049
- ${resolveErrors.join("\n")}`;
12050
- }
12051
- const editRequests = resolvedEdits.map((e) => ({
12052
- tableIndex: e.tableIndex,
12053
- row: e.row,
12054
- col: e.col,
12055
- newText: e.newText,
12056
- expectedText: e.expectedText
12057
- }));
12058
- const { buffer: newBuffer, results } = await applyEditsToHwpx(
12059
- new Uint8Array(originalBuffer.buffer),
12060
- editRequests
12061
- );
12062
- const failedResults = results.map((r, i) => ({ r, i })).filter(({ r }) => !r.success);
12063
- if (failedResults.length > 0) {
12064
- const messages = failedResults.map(({ r, i }) => {
12065
- const e = resolvedEdits[i];
12066
- const label = e?.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 ` : "";
12067
- return `\uD3B8\uC9D1 #${i + 1} (${label}\uD45C ${e?.tableIndex ?? "?"}, \uD589 ${e?.row ?? "?"}, \uC5F4 ${e?.col ?? "?"}): ${r.error}`;
12068
- });
12069
- return `\uC624\uB958: \uB2E4\uC74C \uD3B8\uC9D1\uC744 \uC801\uC6A9\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
12070
- ${messages.join("\n")}`;
12071
- }
12072
- const diffLines = ["| \uD45C\xB7\uC140 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12073
- for (let i = 0; i < resolvedEdits.length; i++) {
12074
- const e = resolvedEdits[i];
12075
- if (!e) continue;
12076
- const oldText = results[i]?.oldText ?? "";
12077
- const addr = e.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 #${e.tableIndex} (${e.row},${e.col})` : `#${e.tableIndex} (${e.row},${e.col})`;
12078
- diffLines.push(`| ${addr} | ${oldText} | ${e.newText} |`);
12079
- }
12080
- const diff = diffLines.join("\n");
12081
- const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
12082
- const stagedPath = await stageFile(ctx.sessionId, safePath, newBuffer);
11849
+ const stagedPath = await stageFile(ctx.sessionId, outPath, stagedData);
11850
+ const preview = format === "html" ? `[HTML \uB0B4\uBCF4\uB0B4\uAE30 \uBBF8\uB9AC\uBCF4\uAE30 \u2014 \uBCF8\uBB38 \uD14D\uC2A4\uD2B8]
11851
+
11852
+ ${markdown.length > MAX_PREVIEW_CHARS ? `${markdown.slice(0, MAX_PREVIEW_CHARS)}
11853
+
11854
+ ...\uC774\uD558 \uC0DD\uB7B5` : markdown}` : `[PDF \uB0B4\uBCF4\uB0B4\uAE30] ${input.path} \u2192 ${input.outputPath} (${stagedData.byteLength.toLocaleString()} bytes)`;
12083
11855
  const proposalId = crypto.randomUUID();
12084
11856
  return {
12085
11857
  proposal: {
12086
11858
  id: proposalId,
12087
- kind: "cell-edit",
12088
- targetPath: outputPath,
11859
+ kind: "export",
11860
+ targetPath: outPath,
12089
11861
  stagedPath,
12090
- summary: input.summary,
12091
- diff,
12092
- warnings: [],
12093
- willConvertFormat
11862
+ summary: input.summary ?? `${input.path} \u2192 ${format.toUpperCase()} \uB0B4\uBCF4\uB0B4\uAE30`,
11863
+ diff: preview,
11864
+ warnings
12094
11865
  },
12095
11866
  commit: async () => {
12096
- const backupPath = await backupFile(safePath);
12097
- await commitStaged(stagedPath, outputPath);
12098
- const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
12099
- return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
11867
+ const backupPath = await backupFile(outPath);
11868
+ await commitStaged(stagedPath, outPath);
11869
+ const backupInfo = backupPath ? ` (\uAE30\uC874 \uD30C\uC77C \uBC31\uC5C5: ${backupPath})` : "";
11870
+ return `\uB0B4\uBCF4\uB0B4\uAE30 \uC644\uB8CC: ${outPath}${backupInfo}`;
12100
11871
  }
12101
11872
  };
12102
11873
  }
@@ -12106,9 +11877,6 @@ var findInDocumentSchema = z4.object({
12106
11877
  query: z4.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
12107
11878
  caseSensitive: z4.boolean().optional().describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8 false)")
12108
11879
  });
12109
- function unescapeXml(text3) {
12110
- return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
12111
- }
12112
11880
  function windowText(text3, query, caseSensitive, maxLen = 60) {
12113
11881
  const compare3 = caseSensitive ? text3 : text3.toLowerCase();
12114
11882
  const compareQuery = caseSensitive ? query : query.toLowerCase();
@@ -12123,91 +11891,31 @@ function windowText(text3, query, caseSensitive, maxLen = 60) {
12123
11891
  }
12124
11892
  function findInSectionXmls(xmls, query, caseSensitive) {
12125
11893
  const hits = [];
11894
+ const needle = caseSensitive ? query : query.toLowerCase();
11895
+ const includes = (text3) => (caseSensitive ? text3 : text3.toLowerCase()).includes(needle);
12126
11896
  let globalOffset = 0;
12127
11897
  for (let si = 0; si < xmls.length; si++) {
12128
11898
  const xml = xmls[si] ?? "";
12129
- const tokens = tokenizeHwpxXml(xml);
12130
- let localCount = 0;
12131
- {
12132
- let depth = 0;
12133
- for (const tok of tokens) {
12134
- if (tok.kind === "tbl_open") {
12135
- if (depth === 0) localCount++;
12136
- depth++;
12137
- } else if (tok.kind === "tbl_close") {
12138
- depth--;
12139
- }
12140
- }
12141
- }
12142
- for (let ti = 0; ti < localCount; ti++) {
12143
- const range = findTopLevelTableRange(tokens, ti);
12144
- if (!range) continue;
12145
- const cells = collectDirectTcRanges(tokens, range.start, range.end);
12146
- const tblTokens = tokens.filter((t) => t.pos >= range.start && t.pos < range.end);
12147
- for (const cell of cells) {
12148
- const cellText = readOwnTextFromTc(xml, tokens, cell.start, cell.end);
12149
- const compare3 = caseSensitive ? cellText : cellText.toLowerCase();
12150
- const compareQuery = caseSensitive ? query : query.toLowerCase();
12151
- if (!compare3.includes(compareQuery)) continue;
12152
- const cellTokens = tblTokens.filter((t) => t.pos >= cell.start && t.pos < cell.end);
12153
- const addrTok = cellTokens.find((t) => t.kind === "cell_addr");
12154
- if (addrTok === void 0) continue;
12155
- const row = addrTok.rowAddr ?? 0;
12156
- const col = addrTok.colAddr ?? 0;
11899
+ const scan = scanSectionXml(xml, 0);
11900
+ for (let ti = 0; ti < scan.tables.length; ti++) {
11901
+ const table = scan.tables[ti];
11902
+ if (table === void 0) continue;
11903
+ for (const [key, cell] of table.cellByAnchor) {
11904
+ const cellText = cell.paragraphs.map((p) => p.text).join("");
11905
+ if (!includes(cellText)) continue;
11906
+ const parts = key.split(",");
11907
+ const row = Number(parts[0]);
11908
+ const col = Number(parts[1]);
12157
11909
  const truncated = cellText.length > 60 ? `${cellText.slice(0, 57)}\u2026` : cellText;
12158
- hits.push({
12159
- kind: "\uD45C",
12160
- tableIndex: globalOffset + ti,
12161
- row,
12162
- col,
12163
- text: truncated
12164
- });
11910
+ hits.push({ kind: "\uD45C", tableIndex: globalOffset + ti, row, col, text: truncated });
12165
11911
  }
12166
11912
  }
12167
- {
12168
- const bodySegments = [];
12169
- let tblDepth = 0;
12170
- let segStart = 0;
12171
- for (const tok of tokens) {
12172
- if (tok.kind === "tbl_open") {
12173
- if (tblDepth === 0) {
12174
- if (tok.pos > segStart) {
12175
- bodySegments.push({ start: segStart, end: tok.pos });
12176
- }
12177
- }
12178
- tblDepth++;
12179
- } else if (tok.kind === "tbl_close") {
12180
- tblDepth--;
12181
- if (tblDepth === 0) {
12182
- segStart = tok.end;
12183
- }
12184
- }
12185
- }
12186
- if (segStart < xml.length) {
12187
- bodySegments.push({ start: segStart, end: xml.length });
12188
- }
12189
- const tRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
12190
- for (const seg of bodySegments) {
12191
- const slice = xml.slice(seg.start, seg.end);
12192
- tRe.lastIndex = 0;
12193
- let m = tRe.exec(slice);
12194
- while (m !== null) {
12195
- const rawText = m[1] ?? "";
12196
- const decodedText = unescapeXml(rawText);
12197
- const compare3 = caseSensitive ? decodedText : decodedText.toLowerCase();
12198
- const compareQuery = caseSensitive ? query : query.toLowerCase();
12199
- if (compare3.includes(compareQuery)) {
12200
- hits.push({
12201
- kind: "\uBCF8\uBB38",
12202
- section: si,
12203
- text: windowText(decodedText, query, caseSensitive)
12204
- });
12205
- }
12206
- m = tRe.exec(slice);
12207
- }
11913
+ for (const p of [...scan.bodyParagraphs, ...scan.excludedParagraphs]) {
11914
+ if (p.text.length > 0 && includes(p.text)) {
11915
+ hits.push({ kind: "\uBCF8\uBB38", section: si, text: windowText(p.text, query, caseSensitive) });
12208
11916
  }
12209
11917
  }
12210
- globalOffset += localCount;
11918
+ globalOffset += scan.tables.length;
12211
11919
  }
12212
11920
  return hits;
12213
11921
  }
@@ -12251,12 +11959,12 @@ var findInDocumentTool = {
12251
11959
  } catch {
12252
11960
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
12253
11961
  }
12254
- if (bytes[0] !== 80 || bytes[1] !== 75) {
11962
+ if (!isZipBinary(bytes)) {
12255
11963
  return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12256
11964
  }
12257
11965
  let zip;
12258
11966
  try {
12259
- zip = await import_jszip.default.loadAsync(bytes);
11967
+ zip = await import_jszip2.default.loadAsync(bytes);
12260
11968
  } catch (err) {
12261
11969
  return `\uC624\uB958: .hwpx ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${String(err)}`;
12262
11970
  }
@@ -12287,7 +11995,7 @@ var findInDocumentTool = {
12287
11995
  return formatHits(hits, input.query);
12288
11996
  }
12289
11997
  };
12290
- function escapeXml2(text3) {
11998
+ function escapeXml(text3) {
12291
11999
  return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
12292
12000
  }
12293
12001
  function decodeXml(text3) {
@@ -12439,7 +12147,7 @@ function applyFormObjectEdits(xml, edits) {
12439
12147
  }
12440
12148
  let patchCreated = false;
12441
12149
  if (set.caption !== void 0) {
12442
- const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "caption", escapeXml2(set.caption));
12150
+ const patch = replaceAttrInOpenTag(xml, pos, openTagEnd, "caption", escapeXml(set.caption));
12443
12151
  if (!patch) {
12444
12152
  results[ei] = {
12445
12153
  success: false,
@@ -12467,7 +12175,7 @@ function applyFormObjectEdits(xml, edits) {
12467
12175
  pos,
12468
12176
  openTagEnd,
12469
12177
  "selectedValue",
12470
- escapeXml2(set.selected)
12178
+ escapeXml(set.selected)
12471
12179
  );
12472
12180
  if (!patch) {
12473
12181
  results[ei] = {
@@ -12593,7 +12301,7 @@ function replaceAttrInOpenTag(xml, tagStart, tagEnd, attr, newValue) {
12593
12301
  };
12594
12302
  }
12595
12303
  function replaceEditText(_xml, pos, elementContent, newText) {
12596
- const escaped = escapeXml2(newText);
12304
+ const escaped = escapeXml(newText);
12597
12305
  const selfCloseRe = /<hp:text\s*\/>/;
12598
12306
  const scm = selfCloseRe.exec(elementContent);
12599
12307
  if (scm) {
@@ -12640,7 +12348,7 @@ function validateHwpxBuffer(ext, buffer) {
12640
12348
  if (structuralGuard !== null) {
12641
12349
  return structuralGuard;
12642
12350
  }
12643
- if (buffer[0] !== 80 || buffer[1] !== 75) {
12351
+ if (!isZipBinary(buffer)) {
12644
12352
  return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4.";
12645
12353
  }
12646
12354
  return null;
@@ -12866,26 +12574,483 @@ ${failMessages.join("\n")}`;
12866
12574
  out.file(name, await entry.async("uint8array"));
12867
12575
  }
12868
12576
  }
12869
- const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12870
- const newBuffer = new Uint8Array(buf);
12871
- const typeKo = {
12872
- button: "PushButton",
12873
- checkBox: "CheckBox",
12874
- radioButton: "RadioButton",
12875
- comboBox: "ComboBox",
12876
- edit: "Edit"
12877
- };
12878
- const diffLines = ["| \uC591\uC2DD \uAC1C\uCCB4 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12879
- for (const re of resolvedEdits) {
12880
- const result = successMap.get(re.editIdx);
12881
- const oldVal = result?.oldValue ?? "";
12882
- const oldStr = typeof oldVal === "boolean" ? oldVal ? "CHECKED" : "UNCHECKED" : String(oldVal);
12883
- let newStr;
12884
- if (re.set.caption !== void 0) newStr = re.set.caption;
12885
- else if (re.set.checked !== void 0) newStr = re.set.checked ? "CHECKED" : "UNCHECKED";
12886
- else if (re.set.selected !== void 0) newStr = re.set.selected;
12887
- else newStr = re.set.text ?? "";
12888
- diffLines.push(`| ${re.target.name}(${typeKo[re.target.type]}) | ${oldStr} | ${newStr} |`);
12577
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12578
+ const newBuffer = new Uint8Array(buf);
12579
+ const typeKo = {
12580
+ button: "PushButton",
12581
+ checkBox: "CheckBox",
12582
+ radioButton: "RadioButton",
12583
+ comboBox: "ComboBox",
12584
+ edit: "Edit"
12585
+ };
12586
+ const diffLines = ["| \uC591\uC2DD \uAC1C\uCCB4 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
12587
+ for (const re of resolvedEdits) {
12588
+ const result = successMap.get(re.editIdx);
12589
+ const oldVal = result?.oldValue ?? "";
12590
+ const oldStr = typeof oldVal === "boolean" ? oldVal ? "CHECKED" : "UNCHECKED" : String(oldVal);
12591
+ let newStr;
12592
+ if (re.set.caption !== void 0) newStr = re.set.caption;
12593
+ else if (re.set.checked !== void 0) newStr = re.set.checked ? "CHECKED" : "UNCHECKED";
12594
+ else if (re.set.selected !== void 0) newStr = re.set.selected;
12595
+ else newStr = re.set.text ?? "";
12596
+ diffLines.push(`| ${re.target.name}(${typeKo[re.target.type]}) | ${oldStr} | ${newStr} |`);
12597
+ }
12598
+ const diff = diffLines.join("\n");
12599
+ const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
12600
+ const stagedPath = await stageFile(ctx.sessionId, safePath, newBuffer);
12601
+ const proposalId = crypto.randomUUID();
12602
+ return {
12603
+ proposal: {
12604
+ id: proposalId,
12605
+ kind: "form-object",
12606
+ targetPath: outputPath,
12607
+ stagedPath,
12608
+ summary: input.summary,
12609
+ diff,
12610
+ warnings: [],
12611
+ willConvertFormat
12612
+ },
12613
+ commit: async () => {
12614
+ const backupPath = await backupFile(safePath);
12615
+ await commitStaged(stagedPath, outputPath);
12616
+ const backupInfo = backupPath ? ` (\uBC31\uC5C5: ${backupPath})` : "";
12617
+ return `\uC800\uC7A5 \uC644\uB8CC: ${outputPath}${backupInfo}`;
12618
+ }
12619
+ };
12620
+ }
12621
+ };
12622
+ var DOC_EXTENSIONS = /* @__PURE__ */ new Set([
12623
+ ".hwp",
12624
+ ".hwpx",
12625
+ ".hwpml",
12626
+ ".docx",
12627
+ ".doc",
12628
+ ".xlsx",
12629
+ ".xls",
12630
+ ".pdf",
12631
+ ".pptx",
12632
+ ".ppt",
12633
+ ".md",
12634
+ ".txt"
12635
+ ]);
12636
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
12637
+ "node_modules",
12638
+ ".git",
12639
+ ".DS_Store",
12640
+ "__pycache__",
12641
+ "dist",
12642
+ "build",
12643
+ ".next"
12644
+ ]);
12645
+ var MAX_DEPTH = 4;
12646
+ var MAX_FILES = 500;
12647
+ var listFilesSchema = z6.object({
12648
+ dir: z6.string().optional().describe("\uBAA9\uB85D\uC744 \uC870\uD68C\uD560 \uB514\uB809\uD130\uB9AC (\uBBF8\uC9C0\uC815 \uC2DC cwd \uC804\uCCB4)")
12649
+ });
12650
+ async function collectFiles(dir, cwd, depth, entries) {
12651
+ if (depth > MAX_DEPTH || entries.length >= MAX_FILES) return;
12652
+ let items;
12653
+ try {
12654
+ items = await readdir3(dir);
12655
+ } catch {
12656
+ return;
12657
+ }
12658
+ for (const item of items) {
12659
+ if (entries.length >= MAX_FILES) break;
12660
+ if (item.startsWith(".")) continue;
12661
+ if (SKIP_DIRS.has(item)) continue;
12662
+ const fullPath = join4(dir, item);
12663
+ let info;
12664
+ try {
12665
+ info = await stat3(fullPath);
12666
+ } catch {
12667
+ continue;
12668
+ }
12669
+ const relPath = relative2(cwd, fullPath);
12670
+ const ext = extname6(item).toLowerCase();
12671
+ const isDoc = DOC_EXTENSIONS.has(ext);
12672
+ if (info.isDirectory()) {
12673
+ entries.push({ path: relPath + "/", isDir: true, isDoc: false });
12674
+ await collectFiles(fullPath, cwd, depth + 1, entries);
12675
+ } else if (info.isFile()) {
12676
+ entries.push({ path: relPath, isDir: false, isDoc });
12677
+ }
12678
+ }
12679
+ }
12680
+ var listFilesTool = {
12681
+ name: "list_files",
12682
+ description: "\uD604\uC7AC \uC791\uC5C5 \uB514\uB809\uD130\uB9AC(cwd) \uC774\uD558\uC758 \uD30C\uC77C \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C(.hwp/.hwpx/.docx/.xlsx/.pdf \uB4F1)\uC774 \uCD5C\uC0C1\uB2E8\uC5D0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uAE4A\uC774\uB294 \uCD5C\uB300 4\uB2E8\uACC4\uAE4C\uC9C0\uC785\uB2C8\uB2E4.",
12683
+ inputSchema: listFilesSchema,
12684
+ requiresApproval: false,
12685
+ execute: async ({
12686
+ input,
12687
+ ctx
12688
+ }) => {
12689
+ const targetDir = input.dir ? await resolveSafePath(ctx.cwd, input.dir) : ctx.cwd;
12690
+ const entries = [];
12691
+ await collectFiles(targetDir, ctx.cwd, 0, entries);
12692
+ if (entries.length === 0) {
12693
+ return "\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
12694
+ }
12695
+ const docs = entries.filter((e) => e.isDoc);
12696
+ const dirs = entries.filter((e) => e.isDir);
12697
+ const others = entries.filter((e) => !e.isDoc && !e.isDir);
12698
+ const allSorted = [...docs, ...dirs, ...others];
12699
+ const lines = allSorted.map((e) => {
12700
+ const icon = e.isDir ? "\u{1F4C1}" : e.isDoc ? "\u{1F4C4}" : " ";
12701
+ return `${icon} ${e.path}`;
12702
+ });
12703
+ const truncateNotice = entries.length >= MAX_FILES ? `
12704
+ (\uCD5C\uB300 ${MAX_FILES}\uAC1C\uAE4C\uC9C0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uB354 \uC881\uC740 \uBC94\uC704\uB97C \uC9C0\uC815\uD558\uC138\uC694.)` : "";
12705
+ return lines.join("\n") + truncateNotice;
12706
+ }
12707
+ };
12708
+ var cellEditItemSchema = z7.object({
12709
+ tableIndex: z7.number().int().nonnegative().optional().describe(
12710
+ "\uC88C\uD45C \uBAA8\uB4DC: 0-based \uD45C \uC778\uB371\uC2A4(read_document\uC758 kordoc \uBE14\uB85D \uC21C\uC11C, \uC911\uCCA9\uD45C \uC81C\uC678). \uB808\uC774\uBE14 \uBAA8\uB4DC\uC5D0\uC120 \uD0D0\uC0C9 \uBC94\uC704 \uC81C\uD55C\uC6A9(\uC120\uD0DD)."
12711
+ ),
12712
+ row: z7.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 rowAddr (0-based)"),
12713
+ col: z7.number().int().nonnegative().optional().describe("\uC88C\uD45C \uBAA8\uB4DC: \uC140\uC758 colAddr (0-based)"),
12714
+ label: z7.string().optional().describe(
12715
+ "\uB808\uC774\uBE14 \uBAA8\uB4DC: \uAE30\uC900 \uC140 \uD14D\uC2A4\uD2B8(\uD2B8\uB9BC \uBE44\uAD50). \uC774 \uC140\uC758 direction \uBC29\uD5A5 \uC778\uC811 \uC140\uC5D0 newText\uB97C \uAE30\uB85D. \uC88C\uD45C \uBAA8\uB4DC\uBA74 \uC0DD\uB7B5."
12716
+ ),
12717
+ direction: z7.enum(["right", "below"]).optional().describe("\uB808\uC774\uBE14 \uBAA8\uB4DC \uBC29\uD5A5. \uAE30\uBCF8 right(\uC624\uB978\uCABD \uC140), below(\uC544\uB798 \uC140). \uBCD1\uD569 span \uACE0\uB824."),
12718
+ newText: z7.string().describe("\uC140\uC5D0 \uC4F8 \uC0C8 \uD14D\uC2A4\uD2B8"),
12719
+ expectedText: z7.string().optional().describe(
12720
+ "\uD604\uC7AC \uC140 \uD14D\uC2A4\uD2B8(\uC548\uC804 \uAC80\uC99D\uC6A9). \uBD88\uC77C\uCE58 \uC2DC \uC218\uC815\uD558\uC9C0 \uC54A\uC74C. \uC798\uBABB\uB41C \uC140 \uC218\uC815 \uBC29\uC9C0\uB97C \uC704\uD574 \uAD8C\uC7A5."
12721
+ )
12722
+ }).describe(
12723
+ "\uD3B8\uC9D1 \uD56D\uBAA9. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label[+direction]) \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694. \uB458 \uB2E4 \uC9C0\uC815\uD558\uAC70\uB098 \uB458 \uB2E4 \uC0DD\uB7B5\uD558\uBA74 \uC624\uB958\uC785\uB2C8\uB2E4."
12724
+ );
12725
+ var proposeCellEditSchema = z7.object({
12726
+ path: z7.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
12727
+ edits: z7.array(cellEditItemSchema).min(1).describe(
12728
+ "\uD3B8\uC9D1 \uBAA9\uB85D. \uAC01 \uD56D\uBAA9\uC740 \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label+direction) \uC911 \uD558\uB098"
12729
+ ),
12730
+ summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
12731
+ });
12732
+ function cellOwnText(cell) {
12733
+ return cell.paragraphs.map((p) => p.text).join("");
12734
+ }
12735
+ function cellAt(table, row, col) {
12736
+ return table.cellByAnchor.get(`${row},${col}`);
12737
+ }
12738
+ function buildCellWriteSplices(cell, newText) {
12739
+ const paras = cell.paragraphs;
12740
+ if (paras.length === 0) return null;
12741
+ const firstParagraph = paras[0];
12742
+ if (firstParagraph === void 0) return null;
12743
+ const first = buildParagraphSplices(firstParagraph, newText, void 0);
12744
+ if (first === null) return null;
12745
+ const splices = [...first];
12746
+ for (let i = 1; i < paras.length; i++) {
12747
+ const para = paras[i];
12748
+ if (para === void 0) continue;
12749
+ const clear = buildParagraphSplices(para, "", void 0);
12750
+ if (clear !== null) splices.push(...clear);
12751
+ }
12752
+ return splices;
12753
+ }
12754
+ function applyCellEditsToSectionXml(xml, edits) {
12755
+ const scan = scanSectionXml2(xml, 0);
12756
+ const tables = scan.tables;
12757
+ const results = edits.map(() => ({ success: false }));
12758
+ const allSplices = [];
12759
+ for (let ei = 0; ei < edits.length; ei++) {
12760
+ const edit = edits[ei];
12761
+ if (edit === void 0) continue;
12762
+ const table = tables[edit.tableIndex];
12763
+ if (table === void 0) {
12764
+ results[ei] = {
12765
+ success: false,
12766
+ error: `\uD45C ${edit.tableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 \uC139\uC158\uC5D0 \uCD1D ${tables.length}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
12767
+ };
12768
+ continue;
12769
+ }
12770
+ const cell = cellAt(table, edit.row, edit.col);
12771
+ if (cell === void 0) {
12772
+ results[ei] = {
12773
+ success: false,
12774
+ error: `\uD45C ${edit.tableIndex}\uC5D0\uC11C \uC140 (\uD589 ${edit.row}, \uC5F4 ${edit.col})\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. cellAddr colAddr="${edit.col}" rowAddr="${edit.row}"\uC5D0 \uD574\uB2F9\uD558\uB294 \uC140\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`
12775
+ };
12776
+ continue;
12777
+ }
12778
+ const currentText = cellOwnText(cell);
12779
+ if (edit.expectedText !== void 0 && edit.expectedText !== currentText) {
12780
+ results[ei] = {
12781
+ success: false,
12782
+ oldText: currentText,
12783
+ error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC758 \uD604\uC7AC \uD14D\uC2A4\uD2B8\uAC00 \uC608\uC0C1\uAC12\uACFC \uB2E4\uB985\uB2C8\uB2E4. \uC608\uC0C1: "${edit.expectedText}", \uC2E4\uC81C: "${currentText}". \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`
12784
+ };
12785
+ continue;
12786
+ }
12787
+ const splices = buildCellWriteSplices(cell, edit.newText);
12788
+ if (splices === null) {
12789
+ results[ei] = {
12790
+ success: false,
12791
+ oldText: currentText,
12792
+ error: `\uC140 (\uD45C ${edit.tableIndex}, \uD589 ${edit.row}, \uC5F4 ${edit.col})\uC5D0 \uD14D\uC2A4\uD2B8 \uB7F0\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD3B8\uC9D1 \uAC00\uB2A5\uD55C \uBB38\uB2E8/\uB7F0\uC774 \uC5C6\uC5B4 \uAC12\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC140 \uAD6C\uC870\uB97C \uD655\uC778\uD558\uC138\uC694.`
12793
+ };
12794
+ continue;
12795
+ }
12796
+ allSplices.push(...splices);
12797
+ results[ei] = { success: true, oldText: currentText };
12798
+ }
12799
+ if (results.some((r) => !r.success)) {
12800
+ return { newXml: xml, results };
12801
+ }
12802
+ return { newXml: applySplices(xml, allSplices), results };
12803
+ }
12804
+ function parseAnchor(key) {
12805
+ const [r, c] = key.split(",");
12806
+ return { row: Number(r), col: Number(c) };
12807
+ }
12808
+ function findLabelMatchesInScan(tables, trimmedLabel, startIdx, endIdx) {
12809
+ const out = [];
12810
+ for (let ti = startIdx; ti <= endIdx; ti++) {
12811
+ const table = tables[ti];
12812
+ if (table === void 0) continue;
12813
+ for (const [key, cell] of table.cellByAnchor) {
12814
+ if (cellOwnText(cell).trim() === trimmedLabel) {
12815
+ const { row, col } = parseAnchor(key);
12816
+ out.push({ tableIndex: ti, row, col, colSpan: cell.colSpan, rowSpan: cell.rowSpan });
12817
+ }
12818
+ }
12819
+ }
12820
+ return out;
12821
+ }
12822
+ function targetFromLabel(m, direction) {
12823
+ return direction === "right" ? { row: m.row, col: m.col + m.colSpan } : { row: m.row + m.rowSpan, col: m.col };
12824
+ }
12825
+ var SECTION_RE = /^Contents\/section\d+\.xml$/;
12826
+ async function readSections(zip) {
12827
+ const sectionFiles = Object.keys(zip.files).filter((name) => SECTION_RE.test(name)).sort();
12828
+ const out = [];
12829
+ let globalOffset = 0;
12830
+ for (const name of sectionFiles) {
12831
+ const entry = zip.file(name);
12832
+ const xml = entry ? await entry.async("string") : "";
12833
+ const tblCount = scanSectionXml2(xml, 0).tables.length;
12834
+ out.push({ name, xml, tblCount, globalOffset });
12835
+ globalOffset += tblCount;
12836
+ }
12837
+ return out;
12838
+ }
12839
+ async function applyEditsToHwpx(hwpxBuffer, edits) {
12840
+ const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
12841
+ const sections = await readSections(zip);
12842
+ const totalTables = sections.reduce((a, s) => a + s.tblCount, 0);
12843
+ const sectionEdits = sections.map(() => []);
12844
+ for (let ei = 0; ei < edits.length; ei++) {
12845
+ const edit = edits[ei];
12846
+ if (edit === void 0) continue;
12847
+ for (let si = 0; si < sections.length; si++) {
12848
+ const s = sections[si];
12849
+ if (s === void 0) continue;
12850
+ if (edit.tableIndex >= s.globalOffset && edit.tableIndex < s.globalOffset + s.tblCount) {
12851
+ sectionEdits[si]?.push({
12852
+ tableIndex: edit.tableIndex - s.globalOffset,
12853
+ row: edit.row,
12854
+ col: edit.col,
12855
+ newText: edit.newText,
12856
+ expectedText: edit.expectedText,
12857
+ originalEditIdx: ei
12858
+ });
12859
+ break;
12860
+ }
12861
+ }
12862
+ }
12863
+ const allResults = edits.map((edit, ei) => ({
12864
+ success: false,
12865
+ error: `\uD45C ${edit?.tableIndex ?? ei}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uC5D0 \uCD1D ${totalTables}\uAC1C\uC758 \uCD5C\uC0C1\uC704 \uD45C\uAC00 \uC788\uC2B5\uB2C8\uB2E4.`
12866
+ }));
12867
+ const newSectionXmls = sections.map((s) => s.xml);
12868
+ for (let si = 0; si < sections.length; si++) {
12869
+ const sEdits = sectionEdits[si] ?? [];
12870
+ if (sEdits.length === 0) continue;
12871
+ const srcXml = sections[si]?.xml ?? "";
12872
+ const { newXml, results } = applyCellEditsToSectionXml(srcXml, sEdits);
12873
+ newSectionXmls[si] = newXml;
12874
+ for (let i = 0; i < sEdits.length; i++) {
12875
+ const sEdit = sEdits[i];
12876
+ const res = results[i];
12877
+ if (sEdit && res) allResults[sEdit.originalEditIdx] = res;
12878
+ }
12879
+ }
12880
+ if (allResults.some((r) => !r.success)) {
12881
+ return { buffer: hwpxBuffer, results: allResults };
12882
+ }
12883
+ const out = new import_jszip4.default();
12884
+ const mimetypeEntry = zip.file("mimetype");
12885
+ if (mimetypeEntry) {
12886
+ out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
12887
+ }
12888
+ const nameToIdx = new Map(sections.map((s, i) => [s.name, i]));
12889
+ for (const [name, entry] of Object.entries(zip.files)) {
12890
+ if (name === "mimetype" || entry.dir) continue;
12891
+ const idx = nameToIdx.get(name);
12892
+ if (idx !== void 0) {
12893
+ out.file(name, newSectionXmls[idx] ?? "");
12894
+ } else {
12895
+ out.file(name, await entry.async("uint8array"));
12896
+ }
12897
+ }
12898
+ const buf = await out.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
12899
+ return { buffer: new Uint8Array(buf), results: allResults };
12900
+ }
12901
+ function resolveLabelAcrossSections(sections, label, direction, scopedGlobalTableIndex) {
12902
+ const trimmedLabel = label.trim();
12903
+ const matches = [];
12904
+ for (const s of sections) {
12905
+ const tables = scanSectionXml2(s.xml, 0).tables;
12906
+ let localStart = 0;
12907
+ let localEnd = s.tblCount - 1;
12908
+ if (scopedGlobalTableIndex !== void 0) {
12909
+ const localIdx2 = scopedGlobalTableIndex - s.globalOffset;
12910
+ if (localIdx2 < 0 || localIdx2 >= s.tblCount) continue;
12911
+ localStart = localIdx2;
12912
+ localEnd = localIdx2;
12913
+ }
12914
+ for (const m of findLabelMatchesInScan(tables, trimmedLabel, localStart, localEnd)) {
12915
+ matches.push({
12916
+ globalTableIndex: s.globalOffset + m.tableIndex,
12917
+ row: m.row,
12918
+ col: m.col,
12919
+ colSpan: m.colSpan,
12920
+ rowSpan: m.rowSpan
12921
+ });
12922
+ }
12923
+ }
12924
+ if (matches.length === 0) {
12925
+ const scope = scopedGlobalTableIndex !== void 0 ? `\uD45C ${scopedGlobalTableIndex}` : "\uBB38\uC11C \uB0B4 \uBAA8\uB4E0 \uD45C";
12926
+ return {
12927
+ error: `\uB808\uC774\uBE14 "${label}"\uC744(\uB97C) ${scope}\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694.`
12928
+ };
12929
+ }
12930
+ if (matches.length > 1) {
12931
+ const locs = matches.map((m) => `\uD45C ${m.globalTableIndex} (\uD589 ${m.row}, \uC5F4 ${m.col})`).join(", ");
12932
+ return {
12933
+ error: `\uB808\uC774\uBE14 "${label}"\uC774(\uAC00) \uC5EC\uB7EC \uC140\uC5D0\uC11C \uBC1C\uACAC\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${locs}. tableIndex\uB85C \uD0D0\uC0C9 \uBC94\uC704\uB97C \uC881\uD788\uAC70\uB098 \uC88C\uD45C(row/col)\uB97C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.`
12934
+ };
12935
+ }
12936
+ const match = matches[0];
12937
+ const target = targetFromLabel(match, direction);
12938
+ const section = sections.find(
12939
+ (s) => match.globalTableIndex >= s.globalOffset && match.globalTableIndex < s.globalOffset + s.tblCount
12940
+ );
12941
+ if (section === void 0) {
12942
+ return { error: `\uD45C ${match.globalTableIndex}\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (\uB0B4\uBD80 \uC624\uB958).` };
12943
+ }
12944
+ const localIdx = match.globalTableIndex - section.globalOffset;
12945
+ const table = scanSectionXml2(section.xml, 0).tables[localIdx];
12946
+ if (table === void 0 || cellAt(table, target.row, target.col) === void 0) {
12947
+ const dirLabel = direction === "right" ? "\uC624\uB978\uCABD" : "\uC544\uB798";
12948
+ return {
12949
+ error: `\uB808\uC774\uBE14 "${label}" (\uD45C ${match.globalTableIndex}, \uD589 ${match.row}, \uC5F4 ${match.col})\uC758 ${dirLabel} \uC140 (\uD589 ${target.row}, \uC5F4 ${target.col})\uC774 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. direction \uB610\uB294 \uC88C\uD45C\uB97C \uD655\uC778\uD558\uC138\uC694.`
12950
+ };
12951
+ }
12952
+ return { tableIndex: match.globalTableIndex, row: target.row, col: target.col };
12953
+ }
12954
+ var proposeCellEditTool = {
12955
+ name: "propose_cell_edit",
12956
+ description: "HWPX \uBB38\uC11C\uC758 \uD45C \uC140 \uB0B4\uC6A9\uC744 \uC88C\uD45C/\uB808\uC774\uBE14 \uC9C0\uC815\uC73C\uB85C \uC218\uC815\uD569\uB2C8\uB2E4. \uBCD1\uD569 \uC140(cellSpan/rowSpan)\uC774 \uC788\uB294 \uD45C\uC5D0\uC11C\uB3C4 \uBCD1\uD569 \uAD6C\uC870\uB97C \uC644\uC804\uD788 \uBCF4\uC874\uD569\uB2C8\uB2E4. \uBE48 \uC140\uB3C4 \uCC44\uC6B8 \uC218 \uC788\uC5B4 \uC591\uC2DD(form) \uD3B8\uC9D1\uC5D0 \uC801\uD569\uD569\uB2C8\uB2E4. \uC140 \uC8FC\uC18C \uC9C0\uC815 \uBC29\uBC95: (1) \uC88C\uD45C \uBAA8\uB4DC \u2014 tableIndex + row + col \uC9C1\uC811 \uC9C0\uC815. (2) \uB808\uC774\uBE14 \uBAA8\uB4DC \u2014 label(\uC778\uC811 \uB808\uC774\uBE14 \uC140 \uD14D\uC2A4\uD2B8) + direction(right/below, \uAE30\uBCF8 right) + \uC120\uD0DD\uC801 tableIndex\uB85C \uB808\uC774\uBE14 \uC606/\uC544\uB798 \uC140\uC744 \uC790\uB3D9\uC73C\uB85C \uCC3E\uC544 \uD3B8\uC9D1. \uBCD1\uD569 \uB808\uC774\uBE14 \uC140\uB3C4 colSpan/rowSpan\uC744 \uBC18\uC601\uD558\uC5EC \uB300\uC0C1 \uC140\uC744 \uACC4\uC0B0\uD569\uB2C8\uB2E4. propose_edit/propose_form_fill\uC740 \uB9C8\uD06C\uB2E4\uC6B4 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD\uC73C\uB85C \uBCD1\uD569 \uC140\uC744 \uC18C\uC2E4\uC2DC\uD0A4\uBBC0\uB85C, \uBCD1\uD569 \uC140\uC774 \uC788\uB294 \uD45C\uB97C \uC218\uC815\uD560 \uB54C\uB294 \uC774 \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. .hwpx \uD30C\uC77C \uC804\uC6A9\uC785\uB2C8\uB2E4. .hwp\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC73C\uBA70 Hancom\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uC0AC\uC6A9\uD558\uC138\uC694. \uC218\uC815 \uC804\uC5D0 \uBC18\uB4DC\uC2DC read_document\uB85C \uC6D0\uBCF8\uC744 \uC77D\uACE0, expectedText\uB97C \uC9C0\uC815\uD558\uC5EC \uC548\uC804\uD558\uAC8C \uC218\uC815\uD558\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 diff \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uD568\uAED8 \uC0AC\uC6A9\uC790 \uC2B9\uC778\uC744 \uBC1B\uC740 \uD6C4\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4.",
12957
+ inputSchema: proposeCellEditSchema,
12958
+ requiresApproval: true,
12959
+ propose: async ({
12960
+ input,
12961
+ ctx
12962
+ }) => {
12963
+ const safePath = await resolveSafePath(ctx.cwd, input.path);
12964
+ const ext = extname7(safePath).toLowerCase();
12965
+ if (ext !== ".hwpx" && ext !== ".hwp") {
12966
+ return `\uC624\uB958: propose_cell_edit\uC740 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. \uD45C \uC140 \uC9C1\uC811 \uD3B8\uC9D1\uC740 .hwpx \uD3EC\uB9F7\uC5D0\uC11C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4.`;
12967
+ }
12968
+ let originalBuffer;
12969
+ try {
12970
+ originalBuffer = await readFile7(safePath);
12971
+ } catch {
12972
+ return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uAC70\uB098 read_document\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
12973
+ }
12974
+ const originalBytes = new Uint8Array(originalBuffer.buffer);
12975
+ const structuralGuard = hwpStructuralGuard(ext, originalBytes);
12976
+ if (structuralGuard !== null) {
12977
+ return structuralGuard;
12978
+ }
12979
+ if (!isZipBinary(originalBytes)) {
12980
+ return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
12981
+ }
12982
+ const zipForLabel = await import_jszip4.default.loadAsync(new Uint8Array(originalBuffer.buffer));
12983
+ const sections = await readSections(zipForLabel);
12984
+ const resolvedEdits = [];
12985
+ const resolveErrors = [];
12986
+ for (let i = 0; i < input.edits.length; i++) {
12987
+ const e = input.edits[i];
12988
+ if (!e) continue;
12989
+ if (e.label !== void 0 && (e.row !== void 0 || e.col !== void 0)) {
12990
+ resolveErrors.push(
12991
+ `\uD3B8\uC9D1 #${i + 1}: label\uACFC row/col\uC744 \uB3D9\uC2DC\uC5D0 \uC9C0\uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.`
12992
+ );
12993
+ } else if (e.label !== void 0) {
12994
+ const direction = e.direction ?? "right";
12995
+ const resolved = resolveLabelAcrossSections(sections, e.label, direction, e.tableIndex);
12996
+ if ("error" in resolved) {
12997
+ resolveErrors.push(`\uD3B8\uC9D1 #${i + 1} (\uB808\uC774\uBE14 "${e.label}"): ${resolved.error}`);
12998
+ } else {
12999
+ resolvedEdits.push({
13000
+ tableIndex: resolved.tableIndex,
13001
+ row: resolved.row,
13002
+ col: resolved.col,
13003
+ newText: e.newText,
13004
+ expectedText: e.expectedText,
13005
+ label: e.label
13006
+ });
13007
+ }
13008
+ } else if (e.tableIndex !== void 0 && e.row !== void 0 && e.col !== void 0) {
13009
+ resolvedEdits.push({
13010
+ tableIndex: e.tableIndex,
13011
+ row: e.row,
13012
+ col: e.col,
13013
+ newText: e.newText,
13014
+ expectedText: e.expectedText
13015
+ });
13016
+ } else {
13017
+ resolveErrors.push(
13018
+ `\uD3B8\uC9D1 #${i + 1}: \uC88C\uD45C \uBAA8\uB4DC(tableIndex+row+col) \uB610\uB294 \uB808\uC774\uBE14 \uBAA8\uB4DC(label) \uC911 \uD558\uB098\uB97C \uC644\uC804\uD788 \uC9C0\uC815\uD558\uC138\uC694. read_document\uB85C \uD45C \uB0B4\uC6A9\uC744 \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`
13019
+ );
13020
+ }
13021
+ }
13022
+ if (resolveErrors.length > 0) {
13023
+ return `\uC624\uB958: \uB2E4\uC74C \uB808\uC774\uBE14\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
13024
+ ${resolveErrors.join("\n")}`;
13025
+ }
13026
+ const editRequests = resolvedEdits.map((e) => ({
13027
+ tableIndex: e.tableIndex,
13028
+ row: e.row,
13029
+ col: e.col,
13030
+ newText: e.newText,
13031
+ expectedText: e.expectedText
13032
+ }));
13033
+ const { buffer: newBuffer, results } = await applyEditsToHwpx(
13034
+ new Uint8Array(originalBuffer.buffer),
13035
+ editRequests
13036
+ );
13037
+ const failedResults = results.map((r, i) => ({ r, i })).filter(({ r }) => !r.success);
13038
+ if (failedResults.length > 0) {
13039
+ const messages = failedResults.map(({ r, i }) => {
13040
+ const e = resolvedEdits[i];
13041
+ const label = e?.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 ` : "";
13042
+ return `\uD3B8\uC9D1 #${i + 1} (${label}\uD45C ${e?.tableIndex ?? "?"}, \uD589 ${e?.row ?? "?"}, \uC5F4 ${e?.col ?? "?"}): ${r.error}`;
13043
+ });
13044
+ return `\uC624\uB958: \uB2E4\uC74C \uD3B8\uC9D1\uC744 \uC801\uC6A9\uD560 \uC218 \uC5C6\uC5B4 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
13045
+ ${messages.join("\n")}`;
13046
+ }
13047
+ const diffLines = ["| \uD45C\xB7\uC140 | \uC774\uC804 | \uC774\uD6C4 |", "| --- | --- | --- |"];
13048
+ for (let i = 0; i < resolvedEdits.length; i++) {
13049
+ const e = resolvedEdits[i];
13050
+ if (!e) continue;
13051
+ const oldText = results[i]?.oldText ?? "";
13052
+ const addr = e.label ? `\uB808\uC774\uBE14 "${e.label}" \u2192 #${e.tableIndex} (${e.row},${e.col})` : `#${e.tableIndex} (${e.row},${e.col})`;
13053
+ diffLines.push(`| ${addr} | ${oldText} | ${e.newText} |`);
12889
13054
  }
12890
13055
  const diff = diffLines.join("\n");
12891
13056
  const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
@@ -12894,7 +13059,7 @@ ${failMessages.join("\n")}`;
12894
13059
  return {
12895
13060
  proposal: {
12896
13061
  id: proposalId,
12897
- kind: "form-object",
13062
+ kind: "cell-edit",
12898
13063
  targetPath: outputPath,
12899
13064
  stagedPath,
12900
13065
  summary: input.summary,
@@ -12911,92 +13076,6 @@ ${failMessages.join("\n")}`;
12911
13076
  };
12912
13077
  }
12913
13078
  };
12914
- var DOC_EXTENSIONS = /* @__PURE__ */ new Set([
12915
- ".hwp",
12916
- ".hwpx",
12917
- ".hwpml",
12918
- ".docx",
12919
- ".doc",
12920
- ".xlsx",
12921
- ".xls",
12922
- ".pdf",
12923
- ".pptx",
12924
- ".ppt",
12925
- ".md",
12926
- ".txt"
12927
- ]);
12928
- var SKIP_DIRS = /* @__PURE__ */ new Set([
12929
- "node_modules",
12930
- ".git",
12931
- ".DS_Store",
12932
- "__pycache__",
12933
- "dist",
12934
- "build",
12935
- ".next"
12936
- ]);
12937
- var MAX_DEPTH = 4;
12938
- var MAX_FILES = 500;
12939
- var listFilesSchema = z6.object({
12940
- dir: z6.string().optional().describe("\uBAA9\uB85D\uC744 \uC870\uD68C\uD560 \uB514\uB809\uD130\uB9AC (\uBBF8\uC9C0\uC815 \uC2DC cwd \uC804\uCCB4)")
12941
- });
12942
- async function collectFiles(dir, cwd, depth, entries) {
12943
- if (depth > MAX_DEPTH || entries.length >= MAX_FILES) return;
12944
- let items;
12945
- try {
12946
- items = await readdir3(dir);
12947
- } catch {
12948
- return;
12949
- }
12950
- for (const item of items) {
12951
- if (entries.length >= MAX_FILES) break;
12952
- if (item.startsWith(".")) continue;
12953
- if (SKIP_DIRS.has(item)) continue;
12954
- const fullPath = join4(dir, item);
12955
- let info;
12956
- try {
12957
- info = await stat3(fullPath);
12958
- } catch {
12959
- continue;
12960
- }
12961
- const relPath = relative2(cwd, fullPath);
12962
- const ext = extname6(item).toLowerCase();
12963
- const isDoc = DOC_EXTENSIONS.has(ext);
12964
- if (info.isDirectory()) {
12965
- entries.push({ path: relPath + "/", isDir: true, isDoc: false });
12966
- await collectFiles(fullPath, cwd, depth + 1, entries);
12967
- } else if (info.isFile()) {
12968
- entries.push({ path: relPath, isDir: false, isDoc });
12969
- }
12970
- }
12971
- }
12972
- var listFilesTool = {
12973
- name: "list_files",
12974
- description: "\uD604\uC7AC \uC791\uC5C5 \uB514\uB809\uD130\uB9AC(cwd) \uC774\uD558\uC758 \uD30C\uC77C \uBAA9\uB85D\uC744 \uBC18\uD658\uD569\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C(.hwp/.hwpx/.docx/.xlsx/.pdf \uB4F1)\uC774 \uCD5C\uC0C1\uB2E8\uC5D0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uAE4A\uC774\uB294 \uCD5C\uB300 4\uB2E8\uACC4\uAE4C\uC9C0\uC785\uB2C8\uB2E4.",
12975
- inputSchema: listFilesSchema,
12976
- requiresApproval: false,
12977
- execute: async ({
12978
- input,
12979
- ctx
12980
- }) => {
12981
- const targetDir = input.dir ? await resolveSafePath(ctx.cwd, input.dir) : ctx.cwd;
12982
- const entries = [];
12983
- await collectFiles(targetDir, ctx.cwd, 0, entries);
12984
- if (entries.length === 0) {
12985
- return "\uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
12986
- }
12987
- const docs = entries.filter((e) => e.isDoc);
12988
- const dirs = entries.filter((e) => e.isDir);
12989
- const others = entries.filter((e) => !e.isDoc && !e.isDir);
12990
- const allSorted = [...docs, ...dirs, ...others];
12991
- const lines = allSorted.map((e) => {
12992
- const icon = e.isDir ? "\u{1F4C1}" : e.isDoc ? "\u{1F4C4}" : " ";
12993
- return `${icon} ${e.path}`;
12994
- });
12995
- const truncateNotice = entries.length >= MAX_FILES ? `
12996
- (\uCD5C\uB300 ${MAX_FILES}\uAC1C\uAE4C\uC9C0 \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uB354 \uC881\uC740 \uBC94\uC704\uB97C \uC9C0\uC815\uD558\uC138\uC694.)` : "";
12997
- return lines.join("\n") + truncateNotice;
12998
- }
12999
- };
13000
13079
  var HEADING_LEVELS = {
13001
13080
  1: HeadingLevel.HEADING_1,
13002
13081
  2: HeadingLevel.HEADING_2,
@@ -13119,10 +13198,10 @@ async function markdownToDocx(markdown) {
13119
13198
  });
13120
13199
  return Packer.toBuffer(doc);
13121
13200
  }
13122
- var proposeEditSchema = z7.object({
13123
- path: z7.string().describe("\uC218\uC815\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13124
- newMarkdown: z7.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD). read_document\uB85C \uC6D0\uBCF8\uC744 \uBA3C\uC800 \uC77D\uC5B4\uC57C \uD568"),
13125
- summary: z7.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13201
+ var proposeEditSchema = z8.object({
13202
+ path: z8.string().describe("\uC218\uC815\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13203
+ newMarkdown: z8.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD). read_document\uB85C \uC6D0\uBCF8\uC744 \uBA3C\uC800 \uC77D\uC5B4\uC57C \uD568"),
13204
+ summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13126
13205
  });
13127
13206
  var proposeEditTool = {
13128
13207
  name: "propose_edit",
@@ -13134,10 +13213,10 @@ var proposeEditTool = {
13134
13213
  ctx
13135
13214
  }) => {
13136
13215
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13137
- const ext = extname7(safePath).toLowerCase();
13216
+ const ext = extname8(safePath).toLowerCase();
13138
13217
  let originalBuffer;
13139
13218
  try {
13140
- originalBuffer = await readFile7(safePath);
13219
+ originalBuffer = await readFile8(safePath);
13141
13220
  } catch {
13142
13221
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uAC70\uB098 read_document\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.`;
13143
13222
  }
@@ -13215,19 +13294,52 @@ ${diff}`;
13215
13294
  };
13216
13295
  }
13217
13296
  };
13218
- var proposeFindReplaceSchema = z8.object({
13219
- path: z8.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13220
- find: z8.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
13221
- replace: z8.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
13222
- caseSensitive: z8.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
13223
- all: z8.boolean().optional().default(true).describe("\uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uAD50\uCCB4\uD560\uC9C0 \uC5EC\uBD80 (\uAE30\uBCF8\uAC12: true). false\uC774\uBA74 \uCCAB \uBC88\uC9F8 \uB9E4\uCE58\uB9CC \uAD50\uCCB4"),
13224
- summary: z8.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13297
+ function collectParasInDocOrder(scan) {
13298
+ const out = [...scan.bodyParagraphs];
13299
+ const walkTable = (t) => {
13300
+ for (const row of t.rows) {
13301
+ for (const cell of row) {
13302
+ out.push(...cell.paragraphs);
13303
+ for (const nested of cell.tables) walkTable(nested);
13304
+ }
13305
+ }
13306
+ };
13307
+ for (const t of scan.tables) walkTable(t);
13308
+ out.push(...scan.excludedParagraphs);
13309
+ for (const t of scan.orphanTables) walkTable(t);
13310
+ return out.sort((a, b) => a.start - b.start);
13311
+ }
13312
+ function applyRangeSplicesToSection(xml, rangeFn) {
13313
+ const scan = scanSectionXml3(xml, 0);
13314
+ const paras = collectParasInDocOrder(scan);
13315
+ const splices = [];
13316
+ let count = 0;
13317
+ for (const p of paras) {
13318
+ const ranges = rangeFn(p.text);
13319
+ for (const r of ranges) {
13320
+ if (r.end <= r.start) continue;
13321
+ const s = buildRangeSplices(p, xml, r.start, r.end, r.replacement);
13322
+ if (s === null) return null;
13323
+ splices.push(...s);
13324
+ count++;
13325
+ }
13326
+ }
13327
+ if (count === 0) return { xml, count: 0 };
13328
+ return { xml: applySplices2(xml, splices), count };
13329
+ }
13330
+ var proposeFindReplaceSchema = z9.object({
13331
+ path: z9.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13332
+ find: z9.string().min(1).describe("\uCC3E\uC744 \uD14D\uC2A4\uD2B8"),
13333
+ replace: z9.string().describe("\uBC14\uAFC0 \uD14D\uC2A4\uD2B8"),
13334
+ caseSensitive: z9.boolean().optional().default(false).describe("\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84 (\uAE30\uBCF8\uAC12: false)"),
13335
+ all: z9.boolean().optional().default(true).describe("\uBAA8\uB4E0 \uD56D\uBAA9\uC744 \uAD50\uCCB4\uD560\uC9C0 \uC5EC\uBD80 (\uAE30\uBCF8\uAC12: true). false\uC774\uBA74 \uCCAB \uBC88\uC9F8 \uB9E4\uCE58\uB9CC \uAD50\uCCB4"),
13336
+ summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13225
13337
  });
13226
13338
  var MAX_DIFF_SAMPLES = 20;
13227
- function escapeXml3(text3) {
13339
+ function escapeXml2(text3) {
13228
13340
  return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13229
13341
  }
13230
- function unescapeXml2(text3) {
13342
+ function unescapeXml(text3) {
13231
13343
  return text3.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
13232
13344
  }
13233
13345
  function makeChangeSnippet(before, after, ctx = 24) {
@@ -13251,7 +13363,7 @@ function collectChangedSnippets(beforeXml, afterXml, maxSamples) {
13251
13363
  const n = Math.min(beforeNodes.length, afterNodes.length);
13252
13364
  for (let i = 0; i < n && out.length < maxSamples; i++) {
13253
13365
  if (beforeNodes[i] !== afterNodes[i]) {
13254
- const snip = makeChangeSnippet(unescapeXml2(beforeNodes[i]), unescapeXml2(afterNodes[i]));
13366
+ const snip = makeChangeSnippet(unescapeXml(beforeNodes[i]), unescapeXml(afterNodes[i]));
13255
13367
  out.push(snip);
13256
13368
  }
13257
13369
  }
@@ -13261,8 +13373,8 @@ function replaceInSectionXml(xml, find, replace, caseSensitive, replaceAll, alre
13261
13373
  if (!replaceAll && alreadyReplaced > 0) {
13262
13374
  return { xml, count: 0 };
13263
13375
  }
13264
- const escapedFind = escapeXml3(find);
13265
- const escapedReplace = escapeXml3(replace);
13376
+ const escapedFind = escapeXml2(find);
13377
+ const escapedReplace = escapeXml2(replace);
13266
13378
  if (escapedFind.length === 0) {
13267
13379
  return { xml, count: 0 };
13268
13380
  }
@@ -13333,8 +13445,33 @@ function countOccurrences(str, sub) {
13333
13445
  }
13334
13446
  return count;
13335
13447
  }
13448
+ function replaceSectionViaSplices(xml, find, replace, caseSensitive, replaceAll, alreadyReplaced) {
13449
+ if (find.length === 0) return { xml, count: 0 };
13450
+ if (!replaceAll && alreadyReplaced > 0) return { xml, count: 0 };
13451
+ const scan = scanSectionXml4(xml, 0);
13452
+ const paras = collectParasInDocOrder(scan);
13453
+ const needle = caseSensitive ? find : find.toLowerCase();
13454
+ const splices = [];
13455
+ let count = 0;
13456
+ for (const p of paras) {
13457
+ if (!replaceAll && alreadyReplaced + count >= 1) break;
13458
+ const hay = caseSensitive ? p.text : p.text.toLowerCase();
13459
+ let idx = hay.indexOf(needle);
13460
+ while (idx !== -1) {
13461
+ if (!replaceAll && alreadyReplaced + count >= 1) break;
13462
+ const s = buildRangeSplices2(p, xml, idx, idx + find.length, replace);
13463
+ if (s === null) return null;
13464
+ splices.push(...s);
13465
+ count++;
13466
+ if (!replaceAll) break;
13467
+ idx = hay.indexOf(needle, idx + needle.length);
13468
+ }
13469
+ }
13470
+ if (count === 0) return { xml, count: 0 };
13471
+ return { xml: applySplices3(xml, splices), count };
13472
+ }
13336
13473
  async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive, replaceAll) {
13337
- const zip = await import_jszip4.default.loadAsync(hwpxBuffer);
13474
+ const zip = await import_jszip5.default.loadAsync(hwpxBuffer);
13338
13475
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13339
13476
  const sectionXmls = [];
13340
13477
  for (const sf of sectionFiles) {
@@ -13346,7 +13483,9 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13346
13483
  const newSectionXmls = [];
13347
13484
  for (let si = 0; si < sectionFiles.length; si++) {
13348
13485
  const srcXml = sectionXmls[si] ?? "";
13349
- const { xml: newXml, count } = replaceInSectionXml(
13486
+ let newXml;
13487
+ let count;
13488
+ const spliced = replaceSectionViaSplices(
13350
13489
  srcXml,
13351
13490
  find,
13352
13491
  replace,
@@ -13354,6 +13493,21 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13354
13493
  replaceAll,
13355
13494
  totalCount
13356
13495
  );
13496
+ if (spliced !== null) {
13497
+ newXml = spliced.xml;
13498
+ count = spliced.count;
13499
+ } else {
13500
+ const fallback = replaceInSectionXml(
13501
+ srcXml,
13502
+ find,
13503
+ replace,
13504
+ caseSensitive,
13505
+ replaceAll,
13506
+ totalCount
13507
+ );
13508
+ newXml = fallback.xml;
13509
+ count = fallback.count;
13510
+ }
13357
13511
  newSectionXmls.push(newXml);
13358
13512
  totalCount += count;
13359
13513
  if (!replaceAll && totalCount >= 1) {
@@ -13378,7 +13532,7 @@ async function applyFindReplaceToHwpx(hwpxBuffer, find, replace, caseSensitive,
13378
13532
  samples.push(snip);
13379
13533
  }
13380
13534
  }
13381
- const out = new import_jszip4.default();
13535
+ const out = new import_jszip5.default();
13382
13536
  const mimetypeEntry = zip.file("mimetype");
13383
13537
  if (mimetypeEntry) {
13384
13538
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -13409,13 +13563,13 @@ var proposeFindReplaceTool = {
13409
13563
  ctx
13410
13564
  }) => {
13411
13565
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13412
- const ext = extname8(safePath).toLowerCase();
13566
+ const ext = extname9(safePath).toLowerCase();
13413
13567
  if (ext !== ".hwpx" && ext !== ".hwp") {
13414
13568
  return `\uC624\uB958: propose_find_replace\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}. .hwpx \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
13415
13569
  }
13416
13570
  let originalBuf;
13417
13571
  try {
13418
- originalBuf = await readFile8(safePath);
13572
+ originalBuf = await readFile9(safePath);
13419
13573
  } catch {
13420
13574
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13421
13575
  }
@@ -13428,7 +13582,7 @@ var proposeFindReplaceTool = {
13428
13582
  if (structuralGuard !== null) {
13429
13583
  return structuralGuard;
13430
13584
  }
13431
- if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13585
+ if (!isZipBinary(originalBytes)) {
13432
13586
  return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
13433
13587
  }
13434
13588
  let newBytes;
@@ -13454,7 +13608,7 @@ var proposeFindReplaceTool = {
13454
13608
  }
13455
13609
  const warnings = [];
13456
13610
  try {
13457
- const exportedResult = await parse2(newBytes.buffer);
13611
+ const exportedResult = await parse(newBytes.buffer);
13458
13612
  if (exportedResult.success) {
13459
13613
  const exportedMd = exportedResult.markdown;
13460
13614
  const normAfter = input.caseSensitive ?? false ? exportedMd : exportedMd.toLowerCase();
@@ -13512,10 +13666,10 @@ var proposeFindReplaceTool = {
13512
13666
  };
13513
13667
  }
13514
13668
  };
13515
- var proposeFormFillSchema = z9.object({
13516
- path: z9.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13517
- fields: z9.record(z9.string(), z9.string()).describe("\uCC44\uC6B8 \uD544\uB4DC \uB9E4\uD551: { \uB77C\uBCA8: \uAC12 }. read_document\uB85C \uBA3C\uC800 \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694"),
13518
- summary: z9.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13669
+ var proposeFormFillSchema = z10.object({
13670
+ path: z10.string().describe("\uC591\uC2DD \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13671
+ fields: z10.record(z10.string(), z10.string()).describe("\uCC44\uC6B8 \uD544\uB4DC \uB9E4\uD551: { \uB77C\uBCA8: \uAC12 }. read_document\uB85C \uBA3C\uC800 \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694"),
13672
+ summary: z10.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13519
13673
  });
13520
13674
  var proposeFormFillTool = {
13521
13675
  name: "propose_form_fill",
@@ -13527,13 +13681,13 @@ var proposeFormFillTool = {
13527
13681
  ctx
13528
13682
  }) => {
13529
13683
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13530
- const ext = extname9(safePath).toLowerCase();
13684
+ const ext = extname10(safePath).toLowerCase();
13531
13685
  if (ext !== ".hwpx" && ext !== ".hwp") {
13532
13686
  return `\uC624\uB958: propose_form_fill\uC740 .hwp/.hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}. .docx \uD30C\uC77C\uC740 propose_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
13533
13687
  }
13534
13688
  let originalBuffer;
13535
13689
  try {
13536
- originalBuffer = await readFile9(safePath);
13690
+ originalBuffer = await readFile10(safePath);
13537
13691
  } catch {
13538
13692
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13539
13693
  }
@@ -13544,7 +13698,7 @@ var proposeFormFillTool = {
13544
13698
  if (structuralGuard !== null) {
13545
13699
  return structuralGuard;
13546
13700
  }
13547
- const parseResult = await parse3(originalBuffer.buffer);
13701
+ const parseResult = await parse(originalBuffer.buffer);
13548
13702
  if (!parseResult.success) {
13549
13703
  const msg = kordocErrorMessage(
13550
13704
  parseResult.code,
@@ -13552,42 +13706,43 @@ var proposeFormFillTool = {
13552
13706
  );
13553
13707
  return `\uC624\uB958: ${msg}`;
13554
13708
  }
13555
- const formResult = extractFormFields(parseResult.blocks);
13556
- const existingFields = new Map(formResult.fields.map((f) => [f.label, f.value]));
13557
- const unknownLabels = Object.keys(input.fields).filter((label) => !existingFields.has(label));
13709
+ const formSchema = extractFormSchema(parseResult.blocks);
13710
+ const existingFields = new Map(formSchema.fields.map((f) => [f.label, f.value]));
13558
13711
  const warnings = [];
13559
- if (unknownLabels.length > 0) {
13560
- warnings.push(
13561
- `\uB2E4\uC74C \uB77C\uBCA8\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${unknownLabels.join(", ")}. read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.`
13562
- );
13563
- }
13564
- let newMarkdown = parseResult.markdown;
13712
+ const fieldTypeKo = {
13713
+ text: "\uD14D\uC2A4\uD2B8",
13714
+ date: "\uB0A0\uC9DC",
13715
+ phone: "\uC804\uD654",
13716
+ email: "\uC774\uBA54\uC77C",
13717
+ amount: "\uAE08\uC561",
13718
+ checkbox: "\uCCB4\uD06C\uBC15\uC2A4",
13719
+ idnum: "\uC8FC\uBBFC\uBC88\uD638"
13720
+ };
13721
+ const availableHint = formSchema.fields.length > 0 ? ` \uCC44\uC6B8 \uC218 \uC788\uB294 \uD544\uB4DC: ${formSchema.fields.map((f) => `${f.label}(${fieldTypeKo[f.type] ?? f.type}${f.required ? ", \uD544\uC218" : ""})`).join(", ")}.` : "";
13565
13722
  const diffLines = ["| \uB77C\uBCA8 | \uC774\uC804 \uAC12 | \uC0C8 \uAC12 |", "| --- | --- | --- |"];
13566
13723
  for (const [label, newValue] of Object.entries(input.fields)) {
13567
13724
  const oldValue = existingFields.get(label) ?? "(\uC5C6\uC74C)";
13568
13725
  diffLines.push(`| ${label} | ${oldValue} | ${newValue} |`);
13569
- const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13570
- const escapedOld = oldValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13571
- if (oldValue !== "(\uC5C6\uC74C)") {
13572
- newMarkdown = newMarkdown.replace(
13573
- new RegExp(`(\\|\\s*${escapedLabel}\\s*\\|\\s*)${escapedOld}(\\s*\\|)`, "g"),
13574
- `$1${newValue}$2`
13575
- );
13576
- }
13577
13726
  }
13578
13727
  const diff = diffLines.join("\n");
13579
- const origU8 = new Uint8Array(
13580
- originalBuffer.buffer,
13728
+ const origAB = originalBuffer.buffer.slice(
13581
13729
  originalBuffer.byteOffset,
13582
- originalBuffer.byteLength
13730
+ originalBuffer.byteOffset + originalBuffer.byteLength
13583
13731
  );
13584
- const patchResult = await patchHwpx2(origU8, newMarkdown);
13585
- if (!patchResult.success || !patchResult.data) {
13586
- return `\uC624\uB958: \uC591\uC2DD \uCC44\uC6B0\uAE30\uB97C \uC801\uC6A9\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${patchResult.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}.`;
13587
- }
13588
- const stagedData = patchResult.data;
13589
- for (const s of patchResult.skipped) {
13590
- warnings.push(`\uC77C\uBD80 \uD56D\uBAA9\uC774 \uC801\uC6A9\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4(${s.reason ?? "\uC0AC\uC720 \uBBF8\uC0C1"}).`);
13732
+ let stagedData;
13733
+ try {
13734
+ const fillResult = await fillHwpx(origAB, input.fields);
13735
+ if (fillResult.unmatched.length > 0) {
13736
+ warnings.push(
13737
+ `\uB2E4\uC74C \uB77C\uBCA8\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${fillResult.unmatched.join(", ")}.${availableHint || " read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694."}`
13738
+ );
13739
+ }
13740
+ if (fillResult.filled.length === 0) {
13741
+ return `\uC624\uB958: \uCC44\uC6CC\uC9C4 \uC591\uC2DD \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uB77C\uBCA8\uC774 \uBB38\uC11C\uC640 \uC77C\uCE58\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694${fillResult.unmatched.length > 0 ? ` (\uBBF8\uB9E4\uCE6D: ${fillResult.unmatched.join(", ")})` : ""}.${availableHint || " read_document\uB85C \uD604\uC7AC \uD544\uB4DC \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694."}`;
13742
+ }
13743
+ stagedData = new Uint8Array(fillResult.buffer);
13744
+ } catch (err) {
13745
+ return `\uC624\uB958: \uC591\uC2DD \uCC44\uC6B0\uAE30\uB97C \uC801\uC6A9\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${err instanceof Error ? err.message : String(err)}.`;
13591
13746
  }
13592
13747
  const { outputPath, willConvertFormat } = resolveOutputPath(safePath);
13593
13748
  const stagedPath = await stageFile(ctx.sessionId, safePath, stagedData);
@@ -13612,9 +13767,9 @@ var proposeFormFillTool = {
13612
13767
  };
13613
13768
  }
13614
13769
  };
13615
- var proposeRedactPiiSchema = z10.object({
13616
- path: z10.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD560 \uBB38\uC11C \uACBD\uB85C"),
13617
- summary: z10.string().optional().describe("\uBCC0\uACBD \uC694\uC57D")
13770
+ var proposeRedactPiiSchema = z11.object({
13771
+ path: z11.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uBE44\uC2DD\uBCC4 \uCC98\uB9AC\uD560 \uBB38\uC11C \uACBD\uB85C"),
13772
+ summary: z11.string().optional().describe("\uBCC0\uACBD \uC694\uC57D")
13618
13773
  });
13619
13774
  function mergeFindings(allFindings) {
13620
13775
  const map = /* @__PURE__ */ new Map();
@@ -13635,8 +13790,31 @@ function mergeFindings(allFindings) {
13635
13790
  }
13636
13791
  return [...map.values()];
13637
13792
  }
13793
+ function redactSectionViaNodes(srcXml) {
13794
+ const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
13795
+ let offset = 0;
13796
+ let result = srcXml;
13797
+ let changed = false;
13798
+ let m = tNodeRe.exec(srcXml);
13799
+ while (m !== null) {
13800
+ const content = m[1];
13801
+ if (content.length > 0) {
13802
+ const { text: redacted } = redactText(content);
13803
+ if (redacted !== content) {
13804
+ const openTagLen = "<hp:t>".length;
13805
+ const contentStart = m.index + offset + openTagLen;
13806
+ const contentEnd = contentStart + content.length;
13807
+ result = result.substring(0, contentStart) + redacted + result.substring(contentEnd);
13808
+ offset += redacted.length - content.length;
13809
+ changed = true;
13810
+ }
13811
+ }
13812
+ m = tNodeRe.exec(srcXml);
13813
+ }
13814
+ return { xml: result, changed };
13815
+ }
13638
13816
  async function applyRedactToHwpx(hwpxBuffer) {
13639
- const zip = await import_jszip5.default.loadAsync(hwpxBuffer);
13817
+ const zip = await import_jszip6.default.loadAsync(hwpxBuffer);
13640
13818
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
13641
13819
  const sectionXmls = [];
13642
13820
  for (const sf of sectionFiles) {
@@ -13648,38 +13826,28 @@ async function applyRedactToHwpx(hwpxBuffer) {
13648
13826
  const allFindings = [];
13649
13827
  let anyChanged = false;
13650
13828
  for (const srcXml of sectionXmls) {
13651
- const tNodeRe = /<hp:t>([\s\S]*?)<\/hp:t>/g;
13652
- const sectionFindings = [];
13653
- let offset = 0;
13654
- let result = srcXml;
13655
- let m = tNodeRe.exec(srcXml);
13656
- while (m !== null) {
13657
- const content = m[1];
13658
- if (content.length === 0) {
13659
- m = tNodeRe.exec(srcXml);
13660
- continue;
13661
- }
13662
- const { text: redacted, findings } = redactText(content);
13663
- if (redacted !== content) {
13664
- const openTagLen = "<hp:t>".length;
13665
- const contentStart = m.index + offset + openTagLen;
13666
- const contentEnd = contentStart + content.length;
13667
- result = result.substring(0, contentStart) + redacted + result.substring(contentEnd);
13668
- offset += redacted.length - content.length;
13669
- anyChanged = true;
13670
- }
13671
- if (findings.length > 0) {
13672
- sectionFindings.push(findings);
13673
- }
13674
- m = tNodeRe.exec(srcXml);
13829
+ const scan = scanSectionXml5(srcXml, 0);
13830
+ for (const p of collectParasInDocOrder(scan)) {
13831
+ const findings = detectPii(p.text);
13832
+ if (findings.length > 0) allFindings.push(findings);
13833
+ }
13834
+ const spliced = applyRangeSplicesToSection(
13835
+ srcXml,
13836
+ (text3) => redactRanges(text3).map((r) => ({ start: r.start, end: r.end, replacement: r.replacement }))
13837
+ );
13838
+ if (spliced !== null) {
13839
+ newSectionXmls.push(spliced.xml);
13840
+ if (spliced.count > 0) anyChanged = true;
13841
+ } else {
13842
+ const fb = redactSectionViaNodes(srcXml);
13843
+ newSectionXmls.push(fb.xml);
13844
+ if (fb.changed) anyChanged = true;
13675
13845
  }
13676
- newSectionXmls.push(result);
13677
- allFindings.push(...sectionFindings);
13678
13846
  }
13679
13847
  if (!anyChanged) {
13680
13848
  return { buffer: hwpxBuffer, findings: mergeFindings(allFindings), changed: false };
13681
13849
  }
13682
- const out = new import_jszip5.default();
13850
+ const out = new import_jszip6.default();
13683
13851
  const mimetypeEntry = zip.file("mimetype");
13684
13852
  if (mimetypeEntry) {
13685
13853
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -13719,7 +13887,7 @@ var proposeRedactPiiTool = {
13719
13887
  ctx
13720
13888
  }) => {
13721
13889
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13722
- const ext = extname10(safePath).toLowerCase();
13890
+ const ext = extname11(safePath).toLowerCase();
13723
13891
  if (ext !== ".hwpx" && ext !== ".hwp" && ext !== ".md" && ext !== ".txt") {
13724
13892
  const hint = ext === ".docx" || ext === ".xlsx" ? " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4." : " .hwpx/.md/.txt\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.";
13725
13893
  return `\uC624\uB958: propose_redact_pii\uB294 .hwpx/.md/.txt \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}.${hint}`;
@@ -13728,7 +13896,7 @@ var proposeRedactPiiTool = {
13728
13896
  if (ext === ".hwpx" || ext === ".hwp") {
13729
13897
  let originalBuf;
13730
13898
  try {
13731
- originalBuf = await readFile10(safePath);
13899
+ originalBuf = await readFile11(safePath);
13732
13900
  } catch {
13733
13901
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13734
13902
  }
@@ -13741,7 +13909,7 @@ var proposeRedactPiiTool = {
13741
13909
  if (structuralGuard !== null) {
13742
13910
  return structuralGuard;
13743
13911
  }
13744
- if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
13912
+ if (!isZipBinary(originalBytes)) {
13745
13913
  return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
13746
13914
  }
13747
13915
  let patchResult;
@@ -13780,7 +13948,7 @@ var proposeRedactPiiTool = {
13780
13948
  }
13781
13949
  let originalText;
13782
13950
  try {
13783
- originalText = await readFile10(safePath, "utf-8");
13951
+ originalText = await readFile11(safePath, "utf-8");
13784
13952
  } catch {
13785
13953
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}`;
13786
13954
  }
@@ -13813,16 +13981,16 @@ var proposeRedactPiiTool = {
13813
13981
  };
13814
13982
  }
13815
13983
  };
13816
- var proposeSheetEditSchema = z11.object({
13817
- path: z11.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13818
- updates: z11.array(
13819
- z11.object({
13820
- sheet: z11.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13821
- cell: z11.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13822
- value: z11.union([z11.string(), z11.number()]).describe("\uC0C8 \uAC12")
13984
+ var proposeSheetEditSchema = z12.object({
13985
+ path: z12.string().describe("\uC218\uC815\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13986
+ updates: z12.array(
13987
+ z12.object({
13988
+ sheet: z12.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
13989
+ cell: z12.string().describe("\uC140 \uC8FC\uC18C (\uC608: A1, B3)"),
13990
+ value: z12.union([z12.string(), z12.number()]).describe("\uC0C8 \uAC12")
13823
13991
  })
13824
13992
  ).min(1).describe("\uC218\uC815\uD560 \uC140 \uBAA9\uB85D. read_document\uB85C \uC6D0\uBCF8 \uC2DC\uD2B8 \uAD6C\uC870\uB97C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694"),
13825
- summary: z11.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13993
+ summary: z12.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13826
13994
  });
13827
13995
  var proposeSheetEditTool = {
13828
13996
  name: "propose_sheet_edit",
@@ -13834,13 +14002,13 @@ var proposeSheetEditTool = {
13834
14002
  ctx
13835
14003
  }) => {
13836
14004
  const safePath = await resolveSafePath(ctx.cwd, input.path);
13837
- const ext = extname11(safePath).toLowerCase();
14005
+ const ext = extname12(safePath).toLowerCase();
13838
14006
  if (ext !== ".xlsx" && ext !== ".xls") {
13839
14007
  return `\uC624\uB958: propose_sheet_edit\uC740 .xlsx/.xls \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C: ${ext}.`;
13840
14008
  }
13841
14009
  let originalBuffer;
13842
14010
  try {
13843
- originalBuffer = await readFile11(safePath);
14011
+ originalBuffer = await readFile12(safePath);
13844
14012
  } catch {
13845
14013
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
13846
14014
  }
@@ -13933,49 +14101,49 @@ function detectStructuralLoss(beforeBlocks, afterBlocks) {
13933
14101
  }
13934
14102
  return { lost: false, detail: "" };
13935
14103
  }
13936
- var insertRowOpSchema = z12.object({
13937
- type: z12.literal("insertRow"),
13938
- row: z12.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
13939
- position: z12.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
14104
+ var insertRowOpSchema = z13.object({
14105
+ type: z13.literal("insertRow"),
14106
+ row: z13.number().int().nonnegative().describe("\uAE30\uC900 \uD589 \uC778\uB371\uC2A4 (0-based)"),
14107
+ position: z13.enum(["above", "below"]).describe("\uC0BD\uC785 \uC704\uCE58: above=row \uC704\uC5D0, below=row \uC544\uB798\uC5D0")
13940
14108
  });
13941
- var deleteRowOpSchema = z12.object({
13942
- type: z12.literal("deleteRow"),
13943
- row: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
14109
+ var deleteRowOpSchema = z13.object({
14110
+ type: z13.literal("deleteRow"),
14111
+ row: z13.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uD589 \uC778\uB371\uC2A4 (0-based)")
13944
14112
  });
13945
- var insertColumnOpSchema = z12.object({
13946
- type: z12.literal("insertColumn"),
13947
- col: z12.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
13948
- position: z12.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
14113
+ var insertColumnOpSchema = z13.object({
14114
+ type: z13.literal("insertColumn"),
14115
+ col: z13.number().int().nonnegative().describe("\uAE30\uC900 \uC5F4 \uC778\uB371\uC2A4 (0-based)"),
14116
+ position: z13.enum(["left", "right"]).describe("\uC0BD\uC785 \uC704\uCE58: left=col \uC67C\uCABD\uC5D0, right=col \uC624\uB978\uCABD\uC5D0")
13949
14117
  });
13950
- var deleteColumnOpSchema = z12.object({
13951
- type: z12.literal("deleteColumn"),
13952
- col: z12.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
14118
+ var deleteColumnOpSchema = z13.object({
14119
+ type: z13.literal("deleteColumn"),
14120
+ col: z13.number().int().nonnegative().describe("\uC0AD\uC81C\uD560 \uC5F4 \uC778\uB371\uC2A4 (0-based)")
13953
14121
  });
13954
- var mergeCellsOpSchema = z12.object({
13955
- type: z12.literal("mergeCells"),
13956
- startRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
13957
- startCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
13958
- endRow: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
13959
- endCol: z12.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
14122
+ var mergeCellsOpSchema = z13.object({
14123
+ type: z13.literal("mergeCells"),
14124
+ startRow: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uD589 (0-based)"),
14125
+ startCol: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uC2DC\uC791 \uC5F4 (0-based)"),
14126
+ endRow: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uD589 (0-based, \uD3EC\uD568)"),
14127
+ endCol: z13.number().int().nonnegative().describe("\uBCD1\uD569 \uB05D \uC5F4 (0-based, \uD3EC\uD568)")
13960
14128
  });
13961
- var operationSchema = z12.discriminatedUnion("type", [
14129
+ var operationSchema = z13.discriminatedUnion("type", [
13962
14130
  insertRowOpSchema,
13963
14131
  deleteRowOpSchema,
13964
14132
  insertColumnOpSchema,
13965
14133
  deleteColumnOpSchema,
13966
14134
  mergeCellsOpSchema
13967
14135
  ]).describe("\uD45C \uAD6C\uC870 \uC5F0\uC0B0. \uB098\uC911 \uC5F0\uC0B0\uC758 row/col \uC778\uB371\uC2A4\uB294 \uC55E \uC5F0\uC0B0 \uC801\uC6A9 \uD6C4 \uC0C1\uD0DC\uB97C \uAE30\uC900\uC73C\uB85C \uD55C\uB2E4.");
13968
- var proposeTableStructureSchema = z12.object({
13969
- path: z12.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
13970
- anchor: z12.string().min(1).describe(
14136
+ var proposeTableStructureSchema = z13.object({
14137
+ path: z13.string().describe("\uC218\uC815\uD560 .hwpx \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14138
+ anchor: z13.string().min(1).describe(
13971
14139
  "\uB300\uC0C1 \uD45C\uB97C \uC2DD\uBCC4\uD558\uB294 \uC575\uCEE4 \uD14D\uC2A4\uD2B8. \uD45C \uC548\uC5D0\uB9CC \uC788\uB294 \uB3C5\uD2B9\uD55C \uC140 \uD14D\uC2A4\uD2B8\uB97C \uC9C0\uC815\uD558\uC138\uC694. (\uBD80\uBD84 \uC77C\uCE58, \uACF5\uBC31 \uD2B8\uB9BC) \u2014 read_document\uB85C \uD655\uC778 \uD6C4 \uC0AC\uC6A9 \uAD8C\uC7A5."
13972
14140
  ),
13973
- operations: z12.array(operationSchema).min(1).describe(
14141
+ operations: z13.array(operationSchema).min(1).describe(
13974
14142
  "\uC801\uC6A9\uD560 \uD45C \uAD6C\uC870 \uC5F0\uC0B0 \uBAA9\uB85D (\uC21C\uC11C\uB300\uB85C \uC2E4\uD589). \uAC01 \uC5F0\uC0B0\uC740 \uC774\uC804 \uC5F0\uC0B0\uC774 \uC801\uC6A9\uB41C \uD6C4\uC758 \uD45C \uC0C1\uD0DC \uAE30\uC900\uC73C\uB85C row/col\uC744 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
13975
14143
  ),
13976
- summary: z12.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
14144
+ summary: z13.string().describe("\uBCC0\uACBD \uC694\uC57D (\uD55C\uAD6D\uC5B4 1-2\uBB38\uC7A5)")
13977
14145
  });
13978
- function tokenizeHwpxXml2(xml) {
14146
+ function tokenizeHwpxXml(xml) {
13979
14147
  const tokens = [];
13980
14148
  const re = /<hp:tbl[\s>]|<\/hp:tbl>|<hp:tr[\s>]|<\/hp:tr>|<hp:tc[\s>]|<\/hp:tc>|<hp:t\/>|<hp:t>|<\/hp:t>|<hp:cellAddr[^/>]*|<hp:cellSpan[^/>]*|<hp:cellSz[^/>]*/g;
13981
14149
  let m = re.exec(xml);
@@ -14165,7 +14333,7 @@ function setCellSpan(tcXml, colSpan, rowSpan) {
14165
14333
  return tcXml.replace(/(colSpan=")(\d+)(")/, `$1${colSpan}$3`).replace(/(rowSpan=")(\d+)(")/, `$1${rowSpan}$3`);
14166
14334
  }
14167
14335
  function insertRowInTbl(tblXml, row, below) {
14168
- const tokens = tokenizeHwpxXml2(tblXml);
14336
+ const tokens = tokenizeHwpxXml(tblXml);
14169
14337
  const tblStart = 0;
14170
14338
  const tblEnd = tblXml.length;
14171
14339
  const rows = parseTableRows(tblXml, tokens, tblStart, tblEnd);
@@ -14207,7 +14375,7 @@ function insertRowInTbl(tblXml, row, below) {
14207
14375
  modifiedTr = modifiedTr.substring(0, tcLocalStart) + tcXml + modifiedTr.substring(tcLocalEnd);
14208
14376
  }
14209
14377
  let result = tblXml;
14210
- const allTokens = tokenizeHwpxXml2(result);
14378
+ const allTokens = tokenizeHwpxXml(result);
14211
14379
  const addrPatches = [];
14212
14380
  for (const tok of allTokens) {
14213
14381
  if (tok.kind === "cell_addr" && tok.rowAddr !== void 0 && tok.rowAddr >= newRowAddr) {
@@ -14220,7 +14388,7 @@ function insertRowInTbl(tblXml, row, below) {
14220
14388
  const newTag = oldTag.replace(/(rowAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
14221
14389
  result = result.substring(0, p.pos) + newTag + result.substring(p.end);
14222
14390
  }
14223
- const resultTokens = tokenizeHwpxXml2(result);
14391
+ const resultTokens = tokenizeHwpxXml(result);
14224
14392
  const resultRows = parseTableRows(result, resultTokens, 0, result.length);
14225
14393
  let insertAfterPos;
14226
14394
  let insertBeforePos;
@@ -14252,7 +14420,7 @@ function insertRowInTbl(tblXml, row, below) {
14252
14420
  return { ok: true, xml: result };
14253
14421
  }
14254
14422
  function deleteRowInTbl(tblXml, row) {
14255
- const tokens = tokenizeHwpxXml2(tblXml);
14423
+ const tokens = tokenizeHwpxXml(tblXml);
14256
14424
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14257
14425
  const dims = parseTblDimensions(tblXml, 0);
14258
14426
  if (rows.length === 0) {
@@ -14289,7 +14457,7 @@ function deleteRowInTbl(tblXml, row) {
14289
14457
  }
14290
14458
  let result = tblXml;
14291
14459
  result = result.substring(0, targetRow.trStart) + result.substring(targetRow.trEnd);
14292
- const afterTokens = tokenizeHwpxXml2(result);
14460
+ const afterTokens = tokenizeHwpxXml(result);
14293
14461
  const addrPatches = [];
14294
14462
  for (const tok of afterTokens) {
14295
14463
  if (tok.kind === "cell_addr" && tok.rowAddr !== void 0 && tok.rowAddr > row) {
@@ -14306,7 +14474,7 @@ function deleteRowInTbl(tblXml, row) {
14306
14474
  return { ok: true, xml: result };
14307
14475
  }
14308
14476
  function insertColumnInTbl(tblXml, col, right) {
14309
- const tokens = tokenizeHwpxXml2(tblXml);
14477
+ const tokens = tokenizeHwpxXml(tblXml);
14310
14478
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14311
14479
  const dims = parseTblDimensions(tblXml, 0);
14312
14480
  if (rows.length === 0) {
@@ -14327,7 +14495,7 @@ function insertColumnInTbl(tblXml, col, right) {
14327
14495
  }
14328
14496
  }
14329
14497
  let result = tblXml;
14330
- const allTokens = tokenizeHwpxXml2(result);
14498
+ const allTokens = tokenizeHwpxXml(result);
14331
14499
  const addrPatches = [];
14332
14500
  for (const tok of allTokens) {
14333
14501
  if (tok.kind === "cell_addr" && tok.colAddr !== void 0 && tok.colAddr >= newColAddr) {
@@ -14340,7 +14508,7 @@ function insertColumnInTbl(tblXml, col, right) {
14340
14508
  const newTag = oldTag.replace(/(colAddr=")(\d+)(")/, `$1${p.newAddr}$3`);
14341
14509
  result = result.substring(0, p.pos) + newTag + result.substring(p.end);
14342
14510
  }
14343
- const resultTokens2 = tokenizeHwpxXml2(result);
14511
+ const resultTokens2 = tokenizeHwpxXml(result);
14344
14512
  const resultRows = parseTableRows(result, resultTokens2, 0, result.length);
14345
14513
  const sortedRows = [...resultRows].sort((a, b) => b.trStart - a.trStart);
14346
14514
  for (const r of sortedRows) {
@@ -14367,7 +14535,7 @@ function insertColumnInTbl(tblXml, col, right) {
14367
14535
  return { ok: true, xml: result };
14368
14536
  }
14369
14537
  function deleteColumnInTbl(tblXml, col) {
14370
- const tokens = tokenizeHwpxXml2(tblXml);
14538
+ const tokens = tokenizeHwpxXml(tblXml);
14371
14539
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14372
14540
  const dims = parseTblDimensions(tblXml, 0);
14373
14541
  if (rows.length === 0) {
@@ -14401,7 +14569,7 @@ function deleteColumnInTbl(tblXml, col) {
14401
14569
  result = result.substring(0, cell.tcStart) + result.substring(cell.tcEnd);
14402
14570
  }
14403
14571
  }
14404
- const afterTokens = tokenizeHwpxXml2(result);
14572
+ const afterTokens = tokenizeHwpxXml(result);
14405
14573
  const addrPatches = [];
14406
14574
  for (const tok of afterTokens) {
14407
14575
  if (tok.kind === "cell_addr" && tok.colAddr !== void 0 && tok.colAddr > col) {
@@ -14424,7 +14592,7 @@ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
14424
14592
  if (startRow > endRow || startCol > endCol) {
14425
14593
  return { ok: false, error: "\uBCD1\uD569 \uBC94\uC704\uAC00 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (start > end)." };
14426
14594
  }
14427
- const tokens = tokenizeHwpxXml2(tblXml);
14595
+ const tokens = tokenizeHwpxXml(tblXml);
14428
14596
  const rows = parseTableRows(tblXml, tokens, 0, tblXml.length);
14429
14597
  const rangeCells = [];
14430
14598
  for (const r of rows) {
@@ -14480,7 +14648,7 @@ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
14480
14648
  for (const c of sortedRemove) {
14481
14649
  result = result.substring(0, c.tcStart) + result.substring(c.tcEnd);
14482
14650
  }
14483
- const afterTokens = tokenizeHwpxXml2(result);
14651
+ const afterTokens = tokenizeHwpxXml(result);
14484
14652
  const afterRows = parseTableRows(result, afterTokens, 0, result.length);
14485
14653
  const afterTopLeft = afterRows.flatMap((r) => r.cells).find((c) => c.rowAddr === startRow && c.colAddr === startCol);
14486
14654
  if (!afterTopLeft) {
@@ -14492,7 +14660,7 @@ function mergeCellsInTbl(tblXml, startRow, startCol, endRow, endCol) {
14492
14660
  return { ok: true, xml: result };
14493
14661
  }
14494
14662
  function findTableByAnchorInXml(xml, anchor) {
14495
- const tokens = tokenizeHwpxXml2(xml);
14663
+ const tokens = tokenizeHwpxXml(xml);
14496
14664
  const allRanges = findAllTopLevelTableRanges(tokens);
14497
14665
  const trimmedAnchor = anchor.trim();
14498
14666
  const matched = [];
@@ -14530,7 +14698,7 @@ function getTblDims(tblXml) {
14530
14698
  return parseTblDimensions(tblXml, 0);
14531
14699
  }
14532
14700
  async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14533
- const zip = await import_jszip6.default.loadAsync(hwpxBuffer);
14701
+ const zip = await import_jszip7.default.loadAsync(hwpxBuffer);
14534
14702
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
14535
14703
  let targetSectionIdx = -1;
14536
14704
  let targetRange = null;
@@ -14589,7 +14757,7 @@ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14589
14757
  const afterDims = getTblDims(tblBlock);
14590
14758
  const newSectionXml = sectionXml.substring(0, targetRange.start) + tblBlock + sectionXml.substring(targetRange.end);
14591
14759
  sectionXmls[targetSectionIdx] = newSectionXml;
14592
- const out = new import_jszip6.default();
14760
+ const out = new import_jszip7.default();
14593
14761
  const mimetypeEntry = zip.file("mimetype");
14594
14762
  if (mimetypeEntry) {
14595
14763
  out.file("mimetype", await mimetypeEntry.async("uint8array"), { compression: "STORE" });
@@ -14613,12 +14781,12 @@ async function applyOpsToHwpx(hwpxBuffer, anchor, operations) {
14613
14781
  };
14614
14782
  }
14615
14783
  async function verifyOutputDims(newBytes, anchor, expectedRowCnt, expectedColCnt) {
14616
- const zip = await import_jszip6.default.loadAsync(newBytes);
14784
+ const zip = await import_jszip7.default.loadAsync(newBytes);
14617
14785
  const sectionFiles = Object.keys(zip.files).filter((name) => /^Contents\/section\d+\.xml$/.test(name)).sort();
14618
14786
  for (const sf of sectionFiles) {
14619
14787
  const entry = zip.file(sf);
14620
14788
  const xml = entry ? await entry.async("string") : "";
14621
- const tokens = tokenizeHwpxXml2(xml);
14789
+ const tokens = tokenizeHwpxXml(xml);
14622
14790
  const allRanges = findAllTopLevelTableRanges(tokens);
14623
14791
  const trimmedAnchor = anchor.trim();
14624
14792
  for (const r of allRanges) {
@@ -14648,13 +14816,13 @@ var proposeTableStructureTool = {
14648
14816
  ctx
14649
14817
  }) => {
14650
14818
  const safePath = await resolveSafePath(ctx.cwd, input.path);
14651
- const ext = extname12(safePath).toLowerCase();
14819
+ const ext = extname13(safePath).toLowerCase();
14652
14820
  if (ext !== ".hwpx" && ext !== ".hwp") {
14653
14821
  return `\uC624\uB958: propose_table_structure\uB294 .hwpx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C \uD655\uC7A5\uC790: ${ext}. .hwpx \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.`;
14654
14822
  }
14655
14823
  let originalBuf;
14656
14824
  try {
14657
- originalBuf = await readFile12(safePath);
14825
+ originalBuf = await readFile13(safePath);
14658
14826
  } catch {
14659
14827
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.path}. \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.`;
14660
14828
  }
@@ -14667,12 +14835,12 @@ var proposeTableStructureTool = {
14667
14835
  if (structuralGuard !== null) {
14668
14836
  return structuralGuard;
14669
14837
  }
14670
- if (originalBuf[0] !== 80 || originalBuf[1] !== 75) {
14838
+ if (!isZipBinary(originalBytes)) {
14671
14839
  return "\uC624\uB958: \uD30C\uC77C\uC774 \uC720\uD6A8\uD55C .hwpx(ZIP) \uD3EC\uB9F7\uC774 \uC544\uB2D9\uB2C8\uB2E4. \uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uAD6C\uD615 .hwp(OLE \uBC14\uC774\uB108\uB9AC) \uD3EC\uB9F7\uC785\uB2C8\uB2E4. \uD55C\uAE00 \uD504\uB85C\uADF8\uB7A8\uC5D0\uC11C .hwpx\uB85C \uC800\uC7A5 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
14672
14840
  }
14673
14841
  let originalBlocks = null;
14674
14842
  try {
14675
- const origResult = await parse4(originalBuf.buffer);
14843
+ const origResult = await parse(originalBuf.buffer);
14676
14844
  if (origResult.success) {
14677
14845
  originalBlocks = origResult.blocks;
14678
14846
  }
@@ -14693,7 +14861,7 @@ var proposeTableStructureTool = {
14693
14861
  let kordocOk = false;
14694
14862
  let exportedBlocks = null;
14695
14863
  try {
14696
- const exportedResult = await parse4(newBytes.buffer);
14864
+ const exportedResult = await parse(newBytes.buffer);
14697
14865
  if (exportedResult.success) {
14698
14866
  exportedMd = exportedResult.markdown;
14699
14867
  exportedBlocks = exportedResult.blocks;
@@ -14854,11 +15022,11 @@ function searchExcerpts(markdown, query) {
14854
15022
  }
14855
15023
  return result;
14856
15024
  }
14857
- var readDocumentSchema = z13.object({
14858
- path: z13.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
14859
- pages: z13.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
14860
- outline: z13.boolean().optional().describe("\uD5E4\uB529(\uC81C\uBAA9) \uAD6C\uC870\uB9CC \uBC18\uD658\uD574 \uBB38\uC11C \uAC1C\uC694 \uD30C\uC545 (\uB300\uD615 \uBB38\uC11C \uD0D0\uC0C9\uC6A9)"),
14861
- search: z13.string().optional().describe("\uD0A4\uC6CC\uB4DC\uAC00 \uD3EC\uD568\uB41C \uBD80\uBD84\uACFC \uC8FC\uBCC0 \uB9E5\uB77D\uB9CC \uBC18\uD658 (\uB300\uD615 \uBB38\uC11C\uC5D0\uC11C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uAE30)")
15025
+ var readDocumentSchema = z14.object({
15026
+ path: z14.string().describe("\uC77D\uC744 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15027
+ pages: z14.string().optional().describe('\uC77D\uC744 \uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: "1-3", "1,3,5") \u2014 \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4'),
15028
+ outline: z14.boolean().optional().describe("\uD5E4\uB529(\uC81C\uBAA9) \uAD6C\uC870\uB9CC \uBC18\uD658\uD574 \uBB38\uC11C \uAC1C\uC694 \uD30C\uC545 (\uB300\uD615 \uBB38\uC11C \uD0D0\uC0C9\uC6A9)"),
15029
+ search: z14.string().optional().describe("\uD0A4\uC6CC\uB4DC\uAC00 \uD3EC\uD568\uB41C \uBD80\uBD84\uACFC \uC8FC\uBCC0 \uB9E5\uB77D\uB9CC \uBC18\uD658 (\uB300\uD615 \uBB38\uC11C\uC5D0\uC11C \uD544\uC694\uD55C \uBD80\uBD84\uB9CC \uC77D\uAE30)")
14862
15030
  });
14863
15031
  function applyReadMode(body, outline, search) {
14864
15032
  if (outline === true) {
@@ -14887,11 +15055,11 @@ var readDocumentTool = {
14887
15055
  const msg = e instanceof Error ? e.message : String(e);
14888
15056
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
14889
15057
  }
14890
- const ext = extname13(safePath).toLowerCase();
15058
+ const ext = extname14(safePath).toLowerCase();
14891
15059
  if (PLAIN_TEXT_EXTS.has(ext)) {
14892
15060
  let raw;
14893
15061
  try {
14894
- raw = await readFile13(safePath, "utf-8");
15062
+ raw = await readFile14(safePath, "utf-8");
14895
15063
  } catch (e) {
14896
15064
  const msg = e instanceof Error ? e.message : String(e);
14897
15065
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -14915,7 +15083,7 @@ var readDocumentTool = {
14915
15083
  \u26A0\uFE0F \uB0B4\uC6A9\uC774 \uB108\uBB34 \uAE38\uC5B4 \uC57D 80,000\uC790\uC5D0\uC11C \uC798\uB838\uC2B5\uB2C8\uB2E4. (\uD3C9\uBB38 \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774\uB77C \uD398\uC774\uC9C0 \uB2E8\uC704 \uBD84\uD560 \uC77D\uAE30\uB294 \uC9C0\uC6D0\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)` : "";
14916
15084
  return `${meta2}${body2}${truncationNotice2}`;
14917
15085
  }
14918
- const result = await parse5(safePath, input.pages ? { pages: input.pages } : void 0);
15086
+ const result = await parse(safePath, input.pages ? { pages: input.pages } : void 0);
14919
15087
  if (!result.success) {
14920
15088
  const msg = kordocErrorMessage(result.code, `\uBB38\uC11C\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${result.error}`);
14921
15089
  return `\uC624\uB958: ${msg}`;
@@ -14991,8 +15159,8 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
14991
15159
  ".csv",
14992
15160
  ".log"
14993
15161
  ]);
14994
- var readFileSchema = z14.object({
14995
- path: z14.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
15162
+ var readFileSchema = z15.object({
15163
+ path: z15.string().describe("\uC77D\uC744 \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)")
14996
15164
  });
14997
15165
  var readFileTool = {
14998
15166
  name: "read_file",
@@ -15016,8 +15184,8 @@ var readFileTool = {
15016
15184
  if (info.size > MAX_SIZE) {
15017
15185
  return `\uC624\uB958: \uD30C\uC77C\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4 (${Math.round(info.size / 1024)}KB). \uCD5C\uB300 256KB\uAE4C\uC9C0 \uC77D\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`;
15018
15186
  }
15019
- const { extname: extname17 } = await import("path");
15020
- const ext = extname17(safePath).toLowerCase();
15187
+ const { extname: extname18 } = await import("path");
15188
+ const ext = extname18(safePath).toLowerCase();
15021
15189
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
15022
15190
  return `\uC624\uB958: '${ext}' \uD615\uC2DD\uC740 \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uB85C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C \uD30C\uC77C\uC774\uB77C\uBA74 read_document \uD234\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
15023
15191
  }
@@ -15032,8 +15200,8 @@ var readFileTool = {
15032
15200
  }
15033
15201
  };
15034
15202
  var PLAIN_TEXT_EXTS2 = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text"]);
15035
- var scanPiiSchema = z15.object({
15036
- path: z15.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
15203
+ var scanPiiSchema = z16.object({
15204
+ path: z16.string().describe("\uAC1C\uC778\uC815\uBCF4\uB97C \uC810\uAC80\uD560 \uBB38\uC11C \uACBD\uB85C")
15037
15205
  });
15038
15206
  var scanPiiTool = {
15039
15207
  name: "scan_pii",
@@ -15051,11 +15219,11 @@ var scanPiiTool = {
15051
15219
  const msg = e instanceof Error ? e.message : String(e);
15052
15220
  return `\uC624\uB958: \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
15053
15221
  }
15054
- const ext = extname14(safePath).toLowerCase();
15222
+ const ext = extname15(safePath).toLowerCase();
15055
15223
  let text3;
15056
15224
  if (PLAIN_TEXT_EXTS2.has(ext)) {
15057
15225
  try {
15058
- text3 = await readFile14(safePath, "utf-8");
15226
+ text3 = await readFile15(safePath, "utf-8");
15059
15227
  } catch (e) {
15060
15228
  const msg = e instanceof Error ? e.message : String(e);
15061
15229
  return `\uC624\uB958: \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -15063,7 +15231,7 @@ var scanPiiTool = {
15063
15231
  } else {
15064
15232
  let parseResult;
15065
15233
  try {
15066
- parseResult = await parse6(safePath);
15234
+ parseResult = await parse(safePath);
15067
15235
  } catch (e) {
15068
15236
  const msg = e instanceof Error ? e.message : String(e);
15069
15237
  return `\uC624\uB958: \uBB38\uC11C\uB97C \uD30C\uC2F1\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`;
@@ -15085,10 +15253,10 @@ var scanPiiTool = {
15085
15253
  return lines.join("\n");
15086
15254
  }
15087
15255
  };
15088
- var MAX_PREVIEW_CHARS = 1e4;
15089
- var writeNewDocumentSchema = z16.object({
15090
- path: z16.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15091
- markdown: z16.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
15256
+ var MAX_PREVIEW_CHARS2 = 1e4;
15257
+ var writeNewDocumentSchema = z17.object({
15258
+ path: z17.string().describe("\uC0DD\uC131\uD560 \uBB38\uC11C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15259
+ markdown: z17.string().describe("\uC0C8 \uBB38\uC11C \uB0B4\uC6A9 (\uB9C8\uD06C\uB2E4\uC6B4 \uD615\uC2DD)")
15092
15260
  });
15093
15261
  var writeNewDocumentTool = {
15094
15262
  name: "write_new_document",
@@ -15100,7 +15268,7 @@ var writeNewDocumentTool = {
15100
15268
  ctx
15101
15269
  }) => {
15102
15270
  const safePath = await resolveSafePath(ctx.cwd, input.path);
15103
- const ext = extname15(safePath).toLowerCase();
15271
+ const ext = extname16(safePath).toLowerCase();
15104
15272
  try {
15105
15273
  await stat6(safePath);
15106
15274
  return `\uC624\uB958: \uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${input.path}. \uAE30\uC874 \uD30C\uC77C\uC744 \uC218\uC815\uD558\uB824\uBA74 propose_edit\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.`;
@@ -15121,9 +15289,9 @@ var writeNewDocumentTool = {
15121
15289
  return `\uC624\uB958: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4: ${ext}. .hwpx, .docx, .md, .txt \uC911 \uD558\uB098\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.`;
15122
15290
  }
15123
15291
  const stagedPath = await stageFile(ctx.sessionId, safePath, stagedData);
15124
- const preview = input.markdown.length > MAX_PREVIEW_CHARS ? `${input.markdown.slice(0, MAX_PREVIEW_CHARS)}
15292
+ const preview = input.markdown.length > MAX_PREVIEW_CHARS2 ? `${input.markdown.slice(0, MAX_PREVIEW_CHARS2)}
15125
15293
 
15126
- ...\uC774\uD558 \uC0DD\uB7B5 (${input.markdown.length - MAX_PREVIEW_CHARS}\uC790 \uB354 \uC788\uC74C)` : input.markdown;
15294
+ ...\uC774\uD558 \uC0DD\uB7B5 (${input.markdown.length - MAX_PREVIEW_CHARS2}\uC790 \uB354 \uC788\uC74C)` : input.markdown;
15127
15295
  const proposalId = crypto.randomUUID();
15128
15296
  return {
15129
15297
  proposal: {
@@ -15144,12 +15312,12 @@ ${preview}`,
15144
15312
  };
15145
15313
  }
15146
15314
  };
15147
- var writeNewSpreadsheetSchema = z17.object({
15148
- path: z17.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15149
- sheets: z17.array(
15150
- z17.object({
15151
- name: z17.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
15152
- rows: z17.array(z17.array(z17.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
15315
+ var writeNewSpreadsheetSchema = z18.object({
15316
+ path: z18.string().describe("\uC0DD\uC131\uD560 XLSX \uD30C\uC77C \uACBD\uB85C (cwd \uAE30\uC900 \uC0C1\uB300 \uACBD\uB85C \uB610\uB294 \uC808\uB300 \uACBD\uB85C)"),
15317
+ sheets: z18.array(
15318
+ z18.object({
15319
+ name: z18.string().describe("\uC2DC\uD2B8 \uC774\uB984"),
15320
+ rows: z18.array(z18.array(z18.string())).describe("\uD589 \uB370\uC774\uD130 \uBC30\uC5F4 (\uAC01 \uD589\uC740 \uC140 \uAC12 \uBC30\uC5F4)")
15153
15321
  })
15154
15322
  ).min(1).describe("\uC0DD\uC131\uD560 \uC2DC\uD2B8 \uBAA9\uB85D")
15155
15323
  });
@@ -15163,7 +15331,7 @@ var writeNewSpreadsheetTool = {
15163
15331
  ctx
15164
15332
  }) => {
15165
15333
  const safePath = await resolveSafePath(ctx.cwd, input.path);
15166
- const ext = extname16(safePath).toLowerCase();
15334
+ const ext = extname17(safePath).toLowerCase();
15167
15335
  if (ext !== ".xlsx") {
15168
15336
  return `\uC624\uB958: write_new_spreadsheet\uC740 .xlsx \uD30C\uC77C\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4. \uD604\uC7AC \uD655\uC7A5\uC790: ${ext}.`;
15169
15337
  }
@@ -15225,6 +15393,7 @@ function createDocTools(_ctx) {
15225
15393
  readFileTool,
15226
15394
  scanPiiTool,
15227
15395
  findInDocumentTool,
15396
+ exportDocumentTool,
15228
15397
  proposeEditTool,
15229
15398
  proposeFormFillTool,
15230
15399
  proposeCellEditTool,
@@ -15241,13 +15410,13 @@ function createDocTools(_ctx) {
15241
15410
  }
15242
15411
 
15243
15412
  // src/index.ts
15244
- import chalk5 from "chalk";
15413
+ import chalk6 from "chalk";
15245
15414
  import { Command } from "commander";
15246
15415
 
15247
15416
  // src/chat.ts
15248
15417
  import * as readline from "readline/promises";
15249
15418
  import { isCancel as isCancel2, select as select2, spinner, text } from "@clack/prompts";
15250
- import chalk2 from "chalk";
15419
+ import chalk3 from "chalk";
15251
15420
 
15252
15421
  // src/approve.ts
15253
15422
  import { isCancel, select } from "@clack/prompts";
@@ -15323,6 +15492,45 @@ function createCliApprovalHandler() {
15323
15492
  };
15324
15493
  }
15325
15494
 
15495
+ // src/usage.ts
15496
+ import chalk2 from "chalk";
15497
+ var MODEL_PRICING = [
15498
+ // Anthropic Claude (USD per 1M tokens)
15499
+ { prefix: "claude-opus-4", price: { input: 15, output: 75 } },
15500
+ { prefix: "claude-sonnet-4", price: { input: 3, output: 15 } },
15501
+ { prefix: "claude-haiku-4", price: { input: 1, output: 5 } }
15502
+ // OpenAI·Google: 정가 미확정 → 미등록(토큰만 표시)
15503
+ ];
15504
+ var PROVIDER_DEFAULT_MODEL = {
15505
+ anthropic: "claude-opus-4-8"
15506
+ };
15507
+ function effectiveModelId(config) {
15508
+ return config.model ?? PROVIDER_DEFAULT_MODEL[config.provider] ?? "";
15509
+ }
15510
+ function resolveTokenPrice(model) {
15511
+ for (const { prefix, price } of MODEL_PRICING) {
15512
+ if (model.startsWith(prefix)) return price;
15513
+ }
15514
+ return null;
15515
+ }
15516
+ function estimateCostUsd(model, inputTokens, outputTokens) {
15517
+ const p = resolveTokenPrice(model);
15518
+ if (!p) return null;
15519
+ return inputTokens / 1e6 * p.input + outputTokens / 1e6 * p.output;
15520
+ }
15521
+ var fmtTok = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
15522
+ function fmtUsd(cost) {
15523
+ if (cost > 0 && cost < 0.01) return `$${cost.toFixed(5)}`;
15524
+ return `$${cost.toFixed(4)}`;
15525
+ }
15526
+ function formatCumulativeUsage(config, inputTokens, outputTokens) {
15527
+ const cost = estimateCostUsd(effectiveModelId(config), inputTokens, outputTokens);
15528
+ const costStr = cost !== null ? ` \xB7 \uCD94\uC815 \uB204\uC801 \uBE44\uC6A9 ${fmtUsd(cost)}` : " \xB7 (\uB2E8\uAC00 \uBBF8\uB4F1\uB85D \u2014 \uD1A0\uD070\uB9CC)";
15529
+ return chalk2.dim(
15530
+ `\uB204\uC801 API \uC0AC\uC6A9: \uC785\uB825 ${fmtTok(inputTokens)} \xB7 \uCD9C\uB825 ${fmtTok(outputTokens)} \uD1A0\uD070${costStr}`
15531
+ );
15532
+ }
15533
+
15326
15534
  // src/chat.ts
15327
15535
  var HELP_TEXT = `
15328
15536
  \uD560 \uC218 \uC788\uB294 \uC77C:
@@ -15336,6 +15544,7 @@ var HELP_TEXT = `
15336
15544
  \uC2AC\uB798\uC2DC \uBA85\uB839:
15337
15545
  /model \u2014 \uD504\uB85C\uBC14\uC774\uB354/\uBAA8\uB378 \uC804\uD658
15338
15546
  /context \u2014 \uD604\uC7AC \uCEE8\uD14D\uC2A4\uD2B8 \uC0AC\uC6A9\uB7C9 \uD45C\uC2DC
15547
+ /usage \u2014 \uB204\uC801 API \uC0AC\uC6A9\uB7C9\xB7\uCD94\uC815 \uBE44\uC6A9 \uD45C\uC2DC (/cost)
15339
15548
  /clear \u2014 \uC0C8 \uC138\uC158 \uC2DC\uC791
15340
15549
  /help \u2014 \uC774 \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
15341
15550
  /exit \u2014 \uC885\uB8CC
@@ -15368,44 +15577,44 @@ async function runChat(opts) {
15368
15577
  const okCount = mcpManager.connectedServerNames.length;
15369
15578
  s.stop(okCount > 0 ? `MCP \uC11C\uBC84 ${okCount}\uAC1C \uC5F0\uACB0\uB428` : "MCP \uC5F0\uACB0 \uC644\uB8CC");
15370
15579
  } else {
15371
- process.stdout.write(chalk2.dim(`${connectMsg}
15580
+ process.stdout.write(chalk3.dim(`${connectMsg}
15372
15581
  `));
15373
15582
  await mcpManager.connect(servers);
15374
15583
  const okCount = mcpManager.connectedServerNames.length;
15375
15584
  process.stdout.write(
15376
- chalk2.dim(`${okCount > 0 ? `MCP \uC11C\uBC84 ${okCount}\uAC1C \uC5F0\uACB0\uB428` : "MCP \uC5F0\uACB0 \uC644\uB8CC"}
15585
+ chalk3.dim(`${okCount > 0 ? `MCP \uC11C\uBC84 ${okCount}\uAC1C \uC5F0\uACB0\uB428` : "MCP \uC5F0\uACB0 \uC644\uB8CC"}
15377
15586
  `)
15378
15587
  );
15379
15588
  }
15380
15589
  }
15381
15590
  for (const s of mcpManager.status()) {
15382
15591
  if (s.state === "failed") {
15383
- process.stdout.write(chalk2.dim(`MCP [${s.name}] \uC5F0\uACB0 \uC2E4\uD328: ${s.reason ?? ""}
15592
+ process.stdout.write(chalk3.dim(`MCP [${s.name}] \uC5F0\uACB0 \uC2E4\uD328: ${s.reason ?? ""}
15384
15593
  `));
15385
15594
  } else if (s.state === "skipped") {
15386
- process.stdout.write(chalk2.dim(`MCP [${s.name}] \uC2A4\uD0B5: ${s.reason ?? ""}
15595
+ process.stdout.write(chalk3.dim(`MCP [${s.name}] \uC2A4\uD0B5: ${s.reason ?? ""}
15387
15596
  `));
15388
15597
  }
15389
15598
  }
15390
15599
  for (const w of mcpManager.warnings) {
15391
- process.stdout.write(chalk2.yellow(`\u26A0 ${w}
15600
+ process.stdout.write(chalk3.yellow(`\u26A0 ${w}
15392
15601
  `));
15393
15602
  }
15394
15603
  }
15395
15604
  let store;
15396
15605
  if (opts.resumeId) {
15397
15606
  store = await SessionStore.load(opts.resumeId);
15398
- process.stdout.write(chalk2.dim(`\uC138\uC158 \uC7AC\uAC1C: ${opts.resumeId}
15607
+ process.stdout.write(chalk3.dim(`\uC138\uC158 \uC7AC\uAC1C: ${opts.resumeId}
15399
15608
  `));
15400
15609
  } else if (opts.continueLatest) {
15401
15610
  const sessions = await listSessions();
15402
15611
  const latest = sessions[0];
15403
15612
  if (!latest) {
15404
- process.stdout.write(chalk2.dim("\uC774\uC804 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8 \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4.\n"));
15613
+ process.stdout.write(chalk3.dim("\uC774\uC804 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0C8 \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4.\n"));
15405
15614
  store = await createNewStore(config, cwd);
15406
15615
  } else {
15407
15616
  store = await SessionStore.load(latest.id);
15408
- process.stdout.write(chalk2.dim(`\uC774\uC804 \uC138\uC158 \uC7AC\uAC1C: ${latest.id}
15617
+ process.stdout.write(chalk3.dim(`\uC774\uC804 \uC138\uC158 \uC7AC\uAC1C: ${latest.id}
15409
15618
  `));
15410
15619
  }
15411
15620
  } else {
@@ -15419,6 +15628,8 @@ async function runChat(opts) {
15419
15628
  let ctrlCCount = 0;
15420
15629
  let currentController = null;
15421
15630
  let lastContextTokens = 0;
15631
+ let cumulativeInputTokens = 0;
15632
+ let cumulativeOutputTokens = 0;
15422
15633
  let sharedActiveInterval = null;
15423
15634
  function clearSharedSpinner() {
15424
15635
  if (!sharedActiveInterval) return;
@@ -15433,30 +15644,30 @@ async function runChat(opts) {
15433
15644
  clearSharedSpinner();
15434
15645
  currentController.abort();
15435
15646
  currentController = null;
15436
- process.stdout.write(chalk2.yellow("\n\uD604\uC7AC \uC751\uB2F5\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15647
+ process.stdout.write(chalk3.yellow("\n\uD604\uC7AC \uC751\uB2F5\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15437
15648
  ctrlCCount = 0;
15438
15649
  } else {
15439
15650
  ctrlCCount++;
15440
15651
  if (ctrlCCount >= 2) {
15441
- process.stdout.write(chalk2.yellow("\n\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15652
+ process.stdout.write(chalk3.yellow("\n\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15442
15653
  rl.close();
15443
15654
  cleanSessionStaging(store.id).catch(() => {
15444
15655
  });
15445
15656
  mcpManager.disconnect().finally(() => process.exit(0));
15446
15657
  } else {
15447
- process.stdout.write(chalk2.dim("\n(\uD55C \uBC88 \uB354 Ctrl+C\uB97C \uB204\uB974\uBA74 \uC885\uB8CC\uB429\uB2C8\uB2E4)\n"));
15658
+ process.stdout.write(chalk3.dim("\n(\uD55C \uBC88 \uB354 Ctrl+C\uB97C \uB204\uB974\uBA74 \uC885\uB8CC\uB429\uB2C8\uB2E4)\n"));
15448
15659
  }
15449
15660
  }
15450
15661
  });
15451
- process.stdout.write(chalk2.bold(`kodocagent \uCC44\uD305 \uC2DC\uC791 (/help\uB85C \uB3C4\uC6C0\uB9D0)
15662
+ process.stdout.write(chalk3.bold(`kodocagent \uCC44\uD305 \uC2DC\uC791 (/help\uB85C \uB3C4\uC6C0\uB9D0)
15452
15663
  `));
15453
15664
  process.stdout.write(
15454
- chalk2.dim(`\uD504\uB85C\uBC14\uC774\uB354: ${config.provider}, \uBAA8\uB378: ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15665
+ chalk3.dim(`\uD504\uB85C\uBC14\uC774\uB354: ${config.provider}, \uBAA8\uB378: ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15455
15666
 
15456
15667
  `)
15457
15668
  );
15458
15669
  process.stdout.write(
15459
- chalk2.dim(
15670
+ chalk3.dim(
15460
15671
  '\uBB38\uC11C\uB97C \uC77D\uACE0 \uD45C\xB7\uC591\uC2DD \uC218\uC815, \uCC3E\uAE30\xB7\uBC14\uAFB8\uAE30, \uB418\uB3CC\uB9AC\uAE30\uAE4C\uC9C0 \uC790\uC5F0\uC5B4\uB85C \uC694\uCCAD\uD558\uC138\uC694. \uC608: "\uC774 \uD45C\uC758 \uD569\uACC4\uB97C \uB2E4\uC2DC \uACC4\uC0B0\uD574\uC918", "\uBC29\uAE08 \uC218\uC815 \uB418\uB3CC\uB824\uC918". \uC790\uC138\uD788: /help\n'
15461
15672
  )
15462
15673
  );
@@ -15474,7 +15685,7 @@ async function runChat(opts) {
15474
15685
  var clearActiveSpinner = clearActiveSpinner2;
15475
15686
  let userInput;
15476
15687
  try {
15477
- userInput = await rl.question(chalk2.green("You: "));
15688
+ userInput = await rl.question(chalk3.green("You: "));
15478
15689
  } catch {
15479
15690
  break;
15480
15691
  }
@@ -15486,15 +15697,28 @@ async function runChat(opts) {
15486
15697
  printContextUsage(lastContextTokens, config.maxContextTokens);
15487
15698
  continue;
15488
15699
  }
15700
+ if (trimmed.toLowerCase() === "/usage" || trimmed.toLowerCase() === "/cost") {
15701
+ if (cumulativeInputTokens === 0 && cumulativeOutputTokens === 0) {
15702
+ process.stdout.write(
15703
+ chalk3.dim("\uC544\uC9C1 API \uC0AC\uC6A9 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uBA74 \uB204\uC801\uB429\uB2C8\uB2E4.\n")
15704
+ );
15705
+ } else {
15706
+ process.stdout.write(
15707
+ `${formatCumulativeUsage(config, cumulativeInputTokens, cumulativeOutputTokens)}
15708
+ `
15709
+ );
15710
+ }
15711
+ continue;
15712
+ }
15489
15713
  const handled = await handleSlashCommand(trimmed, config, store, cwd, rl);
15490
15714
  if (handled === "exit") break;
15491
15715
  if (handled === "new-session") {
15492
15716
  store = await createNewStore(config, cwd);
15493
- process.stdout.write(chalk2.dim("\uC0C8 \uC138\uC158\uC774 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15717
+ process.stdout.write(chalk3.dim("\uC0C8 \uC138\uC158\uC774 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
15494
15718
  } else if (handled && typeof handled === "object" && "config" in handled) {
15495
15719
  config = handled.config;
15496
15720
  process.stdout.write(
15497
- chalk2.dim(`\uBAA8\uB378 \uC804\uD658: ${config.provider} / ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15721
+ chalk3.dim(`\uBAA8\uB378 \uC804\uD658: ${config.provider} / ${config.model ?? "(\uAE30\uBCF8\uAC12)"}
15498
15722
  `)
15499
15723
  );
15500
15724
  }
@@ -15506,7 +15730,7 @@ async function runChat(opts) {
15506
15730
  model = createModel(config);
15507
15731
  } catch (err) {
15508
15732
  const msg = err instanceof Error ? err.message : String(err);
15509
- process.stderr.write(chalk2.red(`\uC624\uB958: ${msg}
15733
+ process.stderr.write(chalk3.red(`\uC624\uB958: ${msg}
15510
15734
  `));
15511
15735
  continue;
15512
15736
  }
@@ -15528,7 +15752,7 @@ async function runChat(opts) {
15528
15752
  });
15529
15753
  await session.loadHistory();
15530
15754
  currentController = new AbortController();
15531
- process.stdout.write(chalk2.bold("Assistant: "));
15755
+ process.stdout.write(chalk3.bold("Assistant: "));
15532
15756
  const isTTY = process.stdout.isTTY === true;
15533
15757
  const SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
15534
15758
  let activeToolLabel = "";
@@ -15543,7 +15767,7 @@ async function runChat(opts) {
15543
15767
  process.stdout.write(event.text);
15544
15768
  hasOutput = true;
15545
15769
  } else if (event.type === "tool-call") {
15546
- process.stdout.write(chalk2.dim(`
15770
+ process.stdout.write(chalk3.dim(`
15547
15771
  \u2699 ${formatToolCall(event.toolName, event.args)}
15548
15772
  `));
15549
15773
  if (isTTY) {
@@ -15555,7 +15779,7 @@ async function runChat(opts) {
15555
15779
  const frame = SPINNER_FRAMES[spinnerFrameIdx % SPINNER_FRAMES.length] ?? "\u280B";
15556
15780
  spinnerFrameIdx++;
15557
15781
  const plainText = `${frame} ${activeToolLabel} \uC2E4\uD589 \uC911\u2026`;
15558
- process.stdout.write(`\r${chalk2.dim(plainText)}`);
15782
+ process.stdout.write(`\r${chalk3.dim(plainText)}`);
15559
15783
  lastSpinnerWidth = plainText.length + 1;
15560
15784
  }, 80);
15561
15785
  }
@@ -15565,23 +15789,29 @@ async function runChat(opts) {
15565
15789
  clearActiveSpinner2();
15566
15790
  const statusLabel = event.isError ? "\uC2E4\uD328" : "\uC644\uB8CC";
15567
15791
  process.stdout.write(
15568
- chalk2.dim(` \u2514 ${activeToolLabel} ${statusLabel} (${elapsedSec}s)
15792
+ chalk3.dim(` \u2514 ${activeToolLabel} ${statusLabel} (${elapsedSec}s)
15569
15793
  `)
15570
15794
  );
15571
15795
  } else if (event.type === "approval-required") {
15572
15796
  } else if (event.type === "turn-complete") {
15573
15797
  if (event.usage) {
15574
15798
  lastContextTokens = event.usage.inputTokens;
15799
+ cumulativeInputTokens += event.usage.inputTokens;
15800
+ cumulativeOutputTokens += event.usage.outputTokens;
15575
15801
  process.stdout.write(
15576
15802
  `
15577
- ${formatContextUsage(event.usage.inputTokens, config.maxContextTokens)} ${chalk2.dim(
15578
- `\uCD9C\uB825 ${event.usage.outputTokens} \uD1A0\uD070`
15803
+ ${formatContextUsage(event.usage.inputTokens, config.maxContextTokens)} ${chalk3.dim(
15804
+ `\uC774\uBC88 \uD134 \uCD9C\uB825 ${event.usage.outputTokens} \uD1A0\uD070`
15579
15805
  )}
15806
+ `
15807
+ );
15808
+ process.stdout.write(
15809
+ `${formatCumulativeUsage(config, cumulativeInputTokens, cumulativeOutputTokens)}
15580
15810
  `
15581
15811
  );
15582
15812
  }
15583
15813
  } else if (event.type === "error") {
15584
- process.stderr.write(chalk2.red(`
15814
+ process.stderr.write(chalk3.red(`
15585
15815
  \uC624\uB958: ${event.message}
15586
15816
  `));
15587
15817
  }
@@ -15595,6 +15825,12 @@ ${formatContextUsage(event.usage.inputTokens, config.maxContextTokens)} ${chalk
15595
15825
  }
15596
15826
  }
15597
15827
  rl.close();
15828
+ if (cumulativeInputTokens > 0 || cumulativeOutputTokens > 0) {
15829
+ process.stdout.write(
15830
+ `${formatCumulativeUsage(config, cumulativeInputTokens, cumulativeOutputTokens)}
15831
+ `
15832
+ );
15833
+ }
15598
15834
  await cleanSessionStaging(store.id).catch(() => {
15599
15835
  });
15600
15836
  await mcpManager.disconnect();
@@ -15636,14 +15872,14 @@ function formatContextUsage(used, budget) {
15636
15872
  const fmt = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
15637
15873
  const pct = budget > 0 ? Math.round(used / budget * 100) : 0;
15638
15874
  const text3 = `\uCEE8\uD14D\uC2A4\uD2B8: ${fmt(used)} / ${fmt(budget)} \uD1A0\uD070 (${pct}%)`;
15639
- if (pct >= 90) return chalk2.red(text3);
15640
- if (pct >= 70) return chalk2.yellow(text3);
15641
- return chalk2.dim(text3);
15875
+ if (pct >= 90) return chalk3.red(text3);
15876
+ if (pct >= 70) return chalk3.yellow(text3);
15877
+ return chalk3.dim(text3);
15642
15878
  }
15643
15879
  function printContextUsage(used, budget) {
15644
15880
  if (used <= 0) {
15645
15881
  process.stdout.write(
15646
- chalk2.dim("\uC544\uC9C1 \uCE21\uC815\uB41C \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uBA74 \uD45C\uC2DC\uB429\uB2C8\uB2E4.\n")
15882
+ chalk3.dim("\uC544\uC9C1 \uCE21\uC815\uB41C \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uBA74 \uD45C\uC2DC\uB429\uB2C8\uB2E4.\n")
15647
15883
  );
15648
15884
  return;
15649
15885
  }
@@ -15664,7 +15900,7 @@ async function handleSlashCommand(cmd, config, _store, _cwd, rl) {
15664
15900
  switch (command) {
15665
15901
  case "/exit":
15666
15902
  case "/quit":
15667
- process.stdout.write(chalk2.yellow("\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15903
+ process.stdout.write(chalk3.yellow("\uC885\uB8CC\uD569\uB2C8\uB2E4.\n"));
15668
15904
  rl.close();
15669
15905
  return "exit";
15670
15906
  case "/help":
@@ -15675,7 +15911,7 @@ async function handleSlashCommand(cmd, config, _store, _cwd, rl) {
15675
15911
  case "/model":
15676
15912
  return handleModelSwitch(config);
15677
15913
  default:
15678
- process.stdout.write(chalk2.yellow(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}. /help\uB85C \uB3C4\uC6C0\uB9D0\uC744 \uD655\uC778\uD558\uC138\uC694.
15914
+ process.stdout.write(chalk3.yellow(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839: ${cmd}. /help\uB85C \uB3C4\uC6C0\uB9D0\uC744 \uD655\uC778\uD558\uC138\uC694.
15679
15915
  `));
15680
15916
  return null;
15681
15917
  }
@@ -15697,7 +15933,7 @@ async function handleModelSwitch(_config) {
15697
15933
  options.push({ value: "__custom__", label: "\uC9C1\uC811 \uC785\uB825..." });
15698
15934
  if (options.length <= 1) {
15699
15935
  process.stdout.write(
15700
- chalk2.yellow("API \uD0A4\uAC00 \uC124\uC815\uB41C \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD0A4\uB97C \uC124\uC815\uD558\uC138\uC694.\n")
15936
+ chalk3.yellow("API \uD0A4\uAC00 \uC124\uC815\uB41C \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD0A4\uB97C \uC124\uC815\uD558\uC138\uC694.\n")
15701
15937
  );
15702
15938
  return null;
15703
15939
  }
@@ -15733,7 +15969,7 @@ async function handleModelSwitch(_config) {
15733
15969
  const isKnown = knownForProvider.includes(modelId2);
15734
15970
  if (!isKnown) {
15735
15971
  process.stdout.write(
15736
- chalk2.dim("\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uBAA8\uB378 ID\uC785\uB2C8\uB2E4 \u2014 \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC9C0\uC6D0\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694\n")
15972
+ chalk3.dim("\uB4F1\uB85D\uB418\uC9C0 \uC54A\uC740 \uBAA8\uB378 ID\uC785\uB2C8\uB2E4 \u2014 \uD504\uB85C\uBC14\uC774\uB354\uAC00 \uC9C0\uC6D0\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694\n")
15737
15973
  );
15738
15974
  }
15739
15975
  const newConfig2 = { ...loadedConfig };
@@ -15755,7 +15991,7 @@ async function saveConfig2(config) {
15755
15991
  }
15756
15992
 
15757
15993
  // src/clean-cmd.ts
15758
- import chalk3 from "chalk";
15994
+ import chalk4 from "chalk";
15759
15995
  async function runClean(opts) {
15760
15996
  const stagingDeleted = await cleanAllStaging();
15761
15997
  let backupDeleted;
@@ -15771,7 +16007,7 @@ async function runClean(opts) {
15771
16007
  }
15772
16008
  const backupNote = opts.all ? "" : ` (\uBCF4\uC874 ${backupKept}\uAC1C)`;
15773
16009
  process.stdout.write(
15774
- chalk3.green(`\u2713 \uC2A4\uD14C\uC774\uC9D5 ${stagingDeleted}\uAC1C, \uBC31\uC5C5 ${backupDeleted}\uAC1C \uC815\uB9AC${backupNote}
16010
+ chalk4.green(`\u2713 \uC2A4\uD14C\uC774\uC9D5 ${stagingDeleted}\uAC1C, \uBC31\uC5C5 ${backupDeleted}\uAC1C \uC815\uB9AC${backupNote}
15775
16011
  `)
15776
16012
  );
15777
16013
  }
@@ -15874,7 +16110,7 @@ async function configShow() {
15874
16110
  }
15875
16111
 
15876
16112
  // src/mcp-cmd.ts
15877
- import chalk4 from "chalk";
16113
+ import chalk5 from "chalk";
15878
16114
  async function mcpList() {
15879
16115
  const config = await loadConfig();
15880
16116
  const cwd = process.cwd();
@@ -15893,22 +16129,22 @@ async function mcpList() {
15893
16129
  return;
15894
16130
  }
15895
16131
  process.stdout.write(
15896
- chalk4.bold("\uC774\uB984".padEnd(20)) + chalk4.bold("\uC0C1\uD0DC".padEnd(12)) + chalk4.bold("\uD234 \uC218".padEnd(8)) + chalk4.bold("\uC0AC\uC720") + "\n"
16132
+ chalk5.bold("\uC774\uB984".padEnd(20)) + chalk5.bold("\uC0C1\uD0DC".padEnd(12)) + chalk5.bold("\uD234 \uC218".padEnd(8)) + chalk5.bold("\uC0AC\uC720") + "\n"
15897
16133
  );
15898
16134
  process.stdout.write("\u2500".repeat(70) + "\n");
15899
16135
  for (const s of statuses) {
15900
- const stateStr = s.state === "connected" ? chalk4.green("\uC5F0\uACB0\uB428") : s.state === "failed" ? chalk4.red("\uC2E4\uD328") : chalk4.yellow("\uC2A4\uD0B5");
16136
+ const stateStr = s.state === "connected" ? chalk5.green("\uC5F0\uACB0\uB428") : s.state === "failed" ? chalk5.red("\uC2E4\uD328") : chalk5.yellow("\uC2A4\uD0B5");
15901
16137
  const toolCount = s.state === "connected" ? String(s.toolCount) : "\u2014";
15902
16138
  const reason = s.reason ?? "";
15903
16139
  process.stdout.write(
15904
16140
  s.name.padEnd(20) + stateStr.padEnd(20) + // chalk adds escape codes so pad more
15905
- toolCount.padEnd(8) + chalk4.dim(reason.slice(0, 50)) + "\n"
16141
+ toolCount.padEnd(8) + chalk5.dim(reason.slice(0, 50)) + "\n"
15906
16142
  );
15907
16143
  }
15908
16144
  if (manager.warnings.length > 0) {
15909
16145
  process.stdout.write("\n");
15910
16146
  for (const w of manager.warnings) {
15911
- process.stdout.write(chalk4.yellow(`\uACBD\uACE0: ${w}
16147
+ process.stdout.write(chalk5.yellow(`\uACBD\uACE0: ${w}
15912
16148
  `));
15913
16149
  }
15914
16150
  }
@@ -15921,7 +16157,7 @@ async function mcpTest(serverName) {
15921
16157
  const isSkipped = skipped.find((s) => s.name === serverName);
15922
16158
  if (isSkipped) {
15923
16159
  process.stderr.write(
15924
- chalk4.yellow(`\uC11C\uBC84 '${serverName}'\uC774(\uAC00) \uC2A4\uD0B5\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${isSkipped.reason}
16160
+ chalk5.yellow(`\uC11C\uBC84 '${serverName}'\uC774(\uAC00) \uC2A4\uD0B5\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${isSkipped.reason}
15925
16161
  `)
15926
16162
  );
15927
16163
  return;
@@ -15929,7 +16165,7 @@ async function mcpTest(serverName) {
15929
16165
  const serverConfig = servers.find((s) => s.name === serverName);
15930
16166
  if (!serverConfig) {
15931
16167
  process.stderr.write(
15932
- chalk4.red(
16168
+ chalk5.red(
15933
16169
  `\uC11C\uBC84 '${serverName}'\uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
15934
16170
  'kodocagent mcp list'\uB85C \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC11C\uBC84\uB97C \uD655\uC778\uD558\uC138\uC694.
15935
16171
  `
@@ -15937,32 +16173,32 @@ async function mcpTest(serverName) {
15937
16173
  );
15938
16174
  return;
15939
16175
  }
15940
- process.stdout.write(chalk4.dim(`\uC11C\uBC84 '${serverName}' \uC5F0\uACB0 \uC911...
16176
+ process.stdout.write(chalk5.dim(`\uC11C\uBC84 '${serverName}' \uC5F0\uACB0 \uC911...
15941
16177
  `));
15942
16178
  if (serverConfig.type === "stdio") {
15943
- process.stdout.write(chalk4.dim(" (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC11C\uBC84 \uB2E4\uC6B4\uB85C\uB4DC\uB85C \uC2DC\uAC04\uC774 \uAC78\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4)\n"));
16179
+ process.stdout.write(chalk5.dim(" (\uCD5C\uCD08 \uC2E4\uD589 \uC2DC \uC11C\uBC84 \uB2E4\uC6B4\uB85C\uB4DC\uB85C \uC2DC\uAC04\uC774 \uAC78\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4)\n"));
15944
16180
  }
15945
16181
  const manager = new McpManager();
15946
16182
  await manager.connect([serverConfig]);
15947
16183
  const statuses = manager.status();
15948
16184
  const status = statuses[0];
15949
16185
  if (!status || status.state !== "connected") {
15950
- process.stderr.write(chalk4.red(`\uC5F0\uACB0 \uC2E4\uD328: ${status?.reason ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}
16186
+ process.stderr.write(chalk5.red(`\uC5F0\uACB0 \uC2E4\uD328: ${status?.reason ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}
15951
16187
  `));
15952
16188
  await manager.disconnect();
15953
16189
  return;
15954
16190
  }
15955
- process.stdout.write(chalk4.green(`\uC5F0\uACB0 \uC131\uACF5! \uD234 ${status.toolCount}\uAC1C
16191
+ process.stdout.write(chalk5.green(`\uC5F0\uACB0 \uC131\uACF5! \uD234 ${status.toolCount}\uAC1C
15956
16192
 
15957
16193
  `));
15958
16194
  const defs = manager.getToolDefinitions();
15959
16195
  if (defs.length === 0) {
15960
16196
  process.stdout.write("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD234\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n");
15961
16197
  } else {
15962
- process.stdout.write(chalk4.bold("\uD234 \uBAA9\uB85D:\n"));
16198
+ process.stdout.write(chalk5.bold("\uD234 \uBAA9\uB85D:\n"));
15963
16199
  for (const def of defs) {
15964
- process.stdout.write(` ${chalk4.cyan(def.name)}
15965
- ${chalk4.dim(def.description)}
16200
+ process.stdout.write(` ${chalk5.cyan(def.name)}
16201
+ ${chalk5.dim(def.description)}
15966
16202
  `);
15967
16203
  }
15968
16204
  }
@@ -16053,10 +16289,10 @@ async function runOnboarding() {
16053
16289
 
16054
16290
  // src/update.ts
16055
16291
  import { spawn } from "child_process";
16056
- import { mkdir as mkdir4, readFile as readFile15, writeFile as writeFile3 } from "fs/promises";
16292
+ import { mkdir as mkdir4, readFile as readFile16, writeFile as writeFile3 } from "fs/promises";
16057
16293
  import { dirname as dirname3 } from "path";
16058
16294
  function compareSemver(a, b) {
16059
- const parse7 = (v) => {
16295
+ const parse2 = (v) => {
16060
16296
  const clean = v.replace(/^v/, "").split("-")[0] ?? "";
16061
16297
  const parts = clean.split(".").map((p) => {
16062
16298
  const n = parseInt(p, 10);
@@ -16064,8 +16300,8 @@ function compareSemver(a, b) {
16064
16300
  });
16065
16301
  return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
16066
16302
  };
16067
- const [aMaj, aMin, aPat] = parse7(a);
16068
- const [bMaj, bMin, bPat] = parse7(b);
16303
+ const [aMaj, aMin, aPat] = parse2(a);
16304
+ const [bMaj, bMin, bPat] = parse2(b);
16069
16305
  if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
16070
16306
  if (aMin !== bMin) return aMin < bMin ? -1 : 1;
16071
16307
  if (aPat !== bPat) return aPat < bPat ? -1 : 1;
@@ -16074,7 +16310,7 @@ function compareSemver(a, b) {
16074
16310
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
16075
16311
  async function readCache(cachePath) {
16076
16312
  try {
16077
- const raw = await readFile15(cachePath, "utf-8");
16313
+ const raw = await readFile16(cachePath, "utf-8");
16078
16314
  const parsed = JSON.parse(raw);
16079
16315
  if (parsed !== null && typeof parsed === "object" && "checkedAt" in parsed && "latest" in parsed && typeof parsed.checkedAt === "string" && typeof parsed.latest === "string") {
16080
16316
  return parsed;
@@ -16196,7 +16432,7 @@ program.option("-p, --print <prompt>", "\uB2E8\uBC1C \uC9C8\uC758 (\uBE44\uB300\
16196
16432
  const newVersion = await checkForUpdate(cliVersion()).catch(() => null);
16197
16433
  if (newVersion) {
16198
16434
  process.stdout.write(
16199
- chalk5.yellow(`\uC0C8 \uBC84\uC804 v${newVersion} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 'kodocagent update'\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694
16435
+ chalk6.yellow(`\uC0C8 \uBC84\uC804 v${newVersion} \uC0AC\uC6A9 \uAC00\uB2A5 \u2014 'kodocagent update'\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694
16200
16436
  `)
16201
16437
  );
16202
16438
  }
@@ -16226,15 +16462,15 @@ program.command("sessions").description("\uC138\uC158 \uBAA9\uB85D \uD45C\uC2DC"
16226
16462
  for (const s of sessions) {
16227
16463
  const dateStr = s.mtime.toLocaleString("ko-KR");
16228
16464
  process.stdout.write(
16229
- `${chalk5.bold(s.id)} ${chalk5.dim(dateStr)} ${chalk5.cyan(s.meta.provider)}/${s.meta.model ?? "(\uAE30\uBCF8)"}
16465
+ `${chalk6.bold(s.id)} ${chalk6.dim(dateStr)} ${chalk6.cyan(s.meta.provider)}/${s.meta.model ?? "(\uAE30\uBCF8)"}
16230
16466
  `
16231
16467
  );
16232
16468
  if (s.preview) {
16233
- process.stdout.write(` ${chalk5.italic(`"${s.preview}"`)}`);
16234
- process.stdout.write(` ${chalk5.dim(s.meta.cwd)}
16469
+ process.stdout.write(` ${chalk6.italic(`"${s.preview}"`)}`);
16470
+ process.stdout.write(` ${chalk6.dim(s.meta.cwd)}
16235
16471
  `);
16236
16472
  } else {
16237
- process.stdout.write(` ${chalk5.dim(s.meta.cwd)}
16473
+ process.stdout.write(` ${chalk6.dim(s.meta.cwd)}
16238
16474
  `);
16239
16475
  }
16240
16476
  }
@@ -16295,7 +16531,7 @@ async function pickSession() {
16295
16531
  }
16296
16532
  const sessions = await listSessions();
16297
16533
  if (sessions.length === 0) {
16298
- process.stdout.write(chalk5.dim("\uC7AC\uAC1C\uD560 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
16534
+ process.stdout.write(chalk6.dim("\uC7AC\uAC1C\uD560 \uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
16299
16535
  return void 0;
16300
16536
  }
16301
16537
  const options = sessions.map((s) => {
@@ -16316,17 +16552,17 @@ async function pickSession() {
16316
16552
  }
16317
16553
  function handleError(err) {
16318
16554
  if (err instanceof KodocError) {
16319
- process.stderr.write(chalk5.red(`\uC624\uB958: ${err.message}
16555
+ process.stderr.write(chalk6.red(`\uC624\uB958: ${err.message}
16320
16556
  `));
16321
16557
  if (err.hint) {
16322
- process.stderr.write(chalk5.yellow(` \u2192 ${err.hint}
16558
+ process.stderr.write(chalk6.yellow(` \u2192 ${err.hint}
16323
16559
  `));
16324
16560
  }
16325
16561
  } else if (err instanceof Error) {
16326
- process.stderr.write(chalk5.red(`\uC624\uB958: ${err.message}
16562
+ process.stderr.write(chalk6.red(`\uC624\uB958: ${err.message}
16327
16563
  `));
16328
16564
  } else {
16329
- process.stderr.write(chalk5.red(`\uC54C \uC218 \uC5C6\uB294 \uC624\uB958: ${String(err)}
16565
+ process.stderr.write(chalk6.red(`\uC54C \uC218 \uC5C6\uB294 \uC624\uB958: ${String(err)}
16330
16566
  `));
16331
16567
  }
16332
16568
  process.exit(1);
@@ -16345,7 +16581,7 @@ async function runSingleTurn(prompt) {
16345
16581
  await mcpManager.connect(servers);
16346
16582
  }
16347
16583
  for (const w of mcpManager.warnings) {
16348
- process.stdout.write(chalk5.yellow(`\u26A0 ${w}
16584
+ process.stdout.write(chalk6.yellow(`\u26A0 ${w}
16349
16585
  `));
16350
16586
  }
16351
16587
  }
@@ -16386,7 +16622,7 @@ async function runSingleTurn(prompt) {
16386
16622
  if (event.type === "text-delta") {
16387
16623
  process.stdout.write(event.text);
16388
16624
  } else if (event.type === "error") {
16389
- process.stderr.write(chalk5.red(`
16625
+ process.stderr.write(chalk6.red(`
16390
16626
  \uC624\uB958: ${event.message}
16391
16627
  `));
16392
16628
  }