@clazic/kordoc 2.7.4 → 2.7.6

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/utils.ts
4
- var VERSION = true ? "2.7.3" : "0.0.0-dev";
4
+ var VERSION = true ? "2.7.6" : "0.0.0-dev";
5
5
  function toArrayBuffer(buf) {
6
6
  if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
7
7
  return buf.buffer;
@@ -105,4 +105,4 @@ export {
105
105
  classifyError,
106
106
  normalizeKordocError
107
107
  };
108
- //# sourceMappingURL=chunk-CIR4TB4K.js.map
108
+ //# sourceMappingURL=chunk-X7UUXEMM.js.map
package/dist/cli.js CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- convertToPdf,
4
3
  detectFormat,
5
4
  markdownToHwpx,
6
5
  markdownToXlsx,
7
6
  parse
8
- } from "./chunk-EJZO6DUI.js";
7
+ } from "./chunk-URSQEMVJ.js";
9
8
  import {
10
9
  VERSION,
11
10
  toArrayBuffer
12
- } from "./chunk-CIR4TB4K.js";
11
+ } from "./chunk-X7UUXEMM.js";
13
12
  import "./chunk-MOL7MDBG.js";
14
13
  import "./chunk-S7BHLD2V.js";
15
14
  import "./chunk-YW5G6BCJ.js";
@@ -174,7 +173,7 @@ async function runParse(files, opts) {
174
173
  saveImages(absPath);
175
174
  }
176
175
  } catch (err) {
177
- const { sanitizeError } = await import("./utils-LYW4Z2Z6.js");
176
+ const { sanitizeError } = await import("./utils-QQVZGOGU.js");
178
177
  process.stderr.write(`
179
178
  [kordoc] ERROR: ${fileName} \u2014 ${sanitizeError(err)}
180
179
  `);
@@ -256,88 +255,7 @@ program.command("convert <input>").description("\uB9C8\uD06C\uB2E4\uC6B4 \uD30C\
256
255
  `));
257
256
  }
258
257
  } catch (err) {
259
- const { sanitizeError } = await import("./utils-LYW4Z2Z6.js");
260
- process.stderr.write(` FAIL
261
- `);
262
- process.stderr.write(` \u2192 ${sanitizeError(err)}
263
- `);
264
- process.exit(1);
265
- }
266
- });
267
- program.command("convert-pdf <input>").description("HWP/HWPX \uD30C\uC77C\uC744 PDF\uB85C \uBCC0\uD658").option("-o, --output <path>", "\uCD9C\uB825 PDF \uACBD\uB85C (\uAE30\uBCF8: \uC785\uB825\uBA85.pdf)").option("-p, --pages <range>", "\uD398\uC774\uC9C0 \uBC94\uC704 (\uC608: 1-3,5)").option("-q, --quality <level>", "\uCD9C\uB825 \uD488\uC9C8 (low|medium|high)").option("--timeout <ms>", "\uBCC0\uD658 \uD0C0\uC784\uC544\uC6C3 (\uAE30\uBCF8: 60000)").option("--soffice-path <path>", "LibreOffice \uBC14\uC774\uB108\uB9AC \uC9C1\uC811 \uC9C0\uC815").option("--no-install", "LibreOffice \uC790\uB3D9 \uC124\uCE58 \uBE44\uD65C\uC131\uD654").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (input, opts) => {
268
- const absInput = resolve(input);
269
- if (!existsSync(absInput)) {
270
- process.stderr.write(`[kordoc] \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}
271
- `);
272
- process.exit(1);
273
- }
274
- const stem = basename(absInput).replace(/\.[^.]+$/, "");
275
- const outPath = opts.output ? resolve(opts.output) : resolve(absInput, "..", `${stem}.pdf`);
276
- if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absInput)} \u2192 ${basename(outPath)} ...`);
277
- try {
278
- const result = await convertToPdf(absInput, {
279
- pages: opts.pages,
280
- timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : void 0,
281
- autoInstallLibreOffice: !opts.noInstall,
282
- sofficePath: opts.sofficePath,
283
- onEvent: opts.silent ? void 0 : (event) => {
284
- switch (event.type) {
285
- case "install": {
286
- const { stage, percent, downloadedBytes, totalBytes } = event;
287
- if (stage === "download_progress" && percent !== void 0 && downloadedBytes !== void 0 && totalBytes !== void 0) {
288
- const filled = Math.round(percent / 5);
289
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
290
- const mb = (downloadedBytes / 1024 / 1024).toFixed(1);
291
- const totalMb = (totalBytes / 1024 / 1024).toFixed(1);
292
- const cr = process.stderr.isTTY ? "\r" : "\n";
293
- process.stderr.write(`${cr}[kordoc] ${basename(absInput)} \u2014 [${bar}] ${percent}% (${mb}MB / ${totalMb}MB)`);
294
- } else if (stage === "install_complete") {
295
- process.stderr.write("\n[kordoc] \uC124\uCE58 \uC644\uB8CC!\n");
296
- } else if (stage === "install_start") {
297
- process.stderr.write(`[kordoc] ${event.message}
298
- `);
299
- }
300
- break;
301
- }
302
- case "convert": {
303
- const { percent } = event;
304
- if (percent !== void 0) {
305
- const filled = Math.round(percent / 5);
306
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
307
- const cr = process.stderr.isTTY ? "\r" : "\n";
308
- process.stderr.write(`${cr}[kordoc] ${basename(absInput)} \u2014 [${bar}] ${percent}%`);
309
- }
310
- break;
311
- }
312
- case "error": {
313
- process.stderr.write(`
314
- [kordoc] \uC624\uB958: ${event.message}
315
- `);
316
- if (event.suggestion) {
317
- process.stderr.write(` \u2192 ${event.suggestion}
318
- `);
319
- }
320
- break;
321
- }
322
- }
323
- }
324
- });
325
- if (!result.success) {
326
- if (!opts.silent) process.stderr.write(` FAIL
327
- `);
328
- process.stderr.write(` \u2192 ${result.error}
329
- `);
330
- process.exit(1);
331
- }
332
- writeFileSync(outPath, result.pdf);
333
- if (!opts.silent) {
334
- process.stderr.write(` OK
335
- `);
336
- process.stderr.write(` \u2192 ${outPath}
337
- `);
338
- }
339
- } catch (err) {
340
- const { sanitizeError } = await import("./utils-LYW4Z2Z6.js");
258
+ const { sanitizeError } = await import("./utils-QQVZGOGU.js");
341
259
  process.stderr.write(` FAIL
342
260
  `);
343
261
  process.stderr.write(` \u2192 ${sanitizeError(err)}
@@ -369,7 +287,7 @@ program.command("init-env").description("kordoc\uC6A9 .env \uD15C\uD50C\uB9BF \u
369
287
  }
370
288
  });
371
289
  program.command("watch <dir>").description("\uB514\uB809\uD1A0\uB9AC \uAC10\uC2DC \u2014 \uC0C8 \uBB38\uC11C \uC790\uB3D9 \uBCC0\uD658").option("--webhook <url>", "\uACB0\uACFC \uC804\uC1A1 \uC6F9\uD6C5 URL").option("-d, --out-dir <dir>", "\uBCC0\uD658 \uACB0\uACFC \uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC").option("-p, --pages <range>", "\uD398\uC774\uC9C0/\uC139\uC158 \uBC94\uC704").option("--format <type>", "\uCD9C\uB825 \uD615\uC2DD: markdown \uB610\uB294 json", "markdown").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (dir, opts) => {
372
- const { watchDirectory } = await import("./watch-CVSZKJE3.js");
290
+ const { watchDirectory } = await import("./watch-RQYUNSSH.js");
373
291
  await watchDirectory({
374
292
  dir,
375
293
  outDir: opts.outDir,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\n\nimport { readFileSync, writeFileSync, mkdirSync, statSync, existsSync, readdirSync } from \"fs\"\nimport { basename, resolve, extname } from \"path\"\nimport { cpus } from \"os\"\nimport { Command } from \"commander\"\nimport { parse, detectFormat, markdownToHwpx, markdownToXlsx, convertToPdf } from \"./index.js\"\nimport type { ParseOptions, OcrMode } from \"./types.js\"\nimport { VERSION, toArrayBuffer } from \"./utils.js\"\nimport { createLoggerFromEnv, generateRunId } from \"./logging/logger.js\"\n\nconst program = new Command()\n\nfunction buildEnvTemplate(): string {\n return [\n \"# kordoc 환경 변수 템플릿\",\n \"# 필요 항목만 주석 해제해서 사용하세요.\",\n \"\",\n \"# ---------- OCR ----------\",\n \"# CLI OCR 모드에서 Ollama 모델 지정\",\n \"# KORDOC_OLLAMA_MODEL=qwen3-vl:8b\",\n \"\",\n \"# Unified OCR 파이프라인 모델 기본값\",\n \"KORDOC_UNIFIED_MODEL_CANDIDATES=mistralai/mistral-medium-3-instruct,moonshotai/kimi-k2.5,qwen/qwen3.5-122b-a10b,qwen/qwen3.5-397b-a17b,moonshotai/kimi-k2-thinking,moonshotai/kimi-k2-instruct,moonshotai/kimi-k2-instruct-0905\",\n \"KORDOC_UNIFIED_MODEL_MAX_TOKENS={\\\"mistralai/mistral-medium-3-instruct\\\":8192,\\\"moonshotai/kimi-k2.5\\\":64000,\\\"qwen/qwen3.5-122b-a10b\\\":64000,\\\"qwen/qwen3.5-397b-a17b\\\":64000,\\\"moonshotai/kimi-k2-thinking\\\":64000,\\\"moonshotai/kimi-k2-instruct\\\":64000,\\\"moonshotai/kimi-k2-instruct-0905\\\":64000}\",\n \"\",\n \"# NIM API 키 (단일/다중)\",\n \"NVIDIA_API_KEY=\",\n \"# NVIDIA_API_KEYS=nvapi-key1,nvapi-key2,nvapi-key3\",\n \"\",\n \"# ---------- 로깅 ----------\",\n \"# KORDOC_LOG_LEVEL=error\",\n \"# KORDOC_LOG_FILE=./logs/kordoc.jsonl\",\n \"# KORDOC_LOG_STACK=0\",\n \"# KORDOC_LOG_PROGRESS_SAMPLE_MS=1000\",\n \"# KORDOC_LOG_BASENAME_PATHS=1\",\n \"# KORDOC_LOG_TEXT_LIMIT=400\",\n \"\",\n ].join(\"\\n\")\n}\n\n/** 공통 parse 옵션 타입 */\ninterface ParseOpts {\n output?: string\n outDir?: string\n pages?: string\n format: string\n headerFooter: boolean\n imageDir?: string\n silent?: boolean\n ocr?: string\n ocrJobs?: string\n ocrBatchSize?: string\n}\n\n/** parse 액션 공통 구현 — 루트 커맨드와 `parse` 서브커맨드가 공유 */\nasync function runParse(files: string[], opts: ParseOpts) {\n const logger = createLoggerFromEnv().withRun(generateRunId(\"cli\")).child({ component: \"cli.ts\" })\n logger.log({ level: \"info\", stage: \"detect\", event: \"start\", message: \"CLI parse 시작\", meta: { files: files.length } })\n const validFormats = [\"markdown\", \"json\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\n process.exit(1)\n }\n for (let fi = 0; fi < files.length; fi++) {\n const filePath = files[fi]\n const absPath = resolve(filePath)\n const fileName = basename(absPath)\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\n\n try {\n const fileSize = statSync(absPath).size\n if (fileSize > 500 * 1024 * 1024) {\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\n process.exitCode = 1\n continue\n }\n const buffer = readFileSync(absPath)\n const arrayBuffer = toArrayBuffer(buffer)\n const format = detectFormat(arrayBuffer)\n\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\n }\n logger.log({ level: \"debug\", stage: \"detect\", event: \"done\", message: \"파일 포맷 감지\", meta: { fileName, format } })\n\n const parseOptions: ParseOptions = {}\n if (opts.pages) parseOptions.pages = opts.pages as string\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\n\n // OCR 모드: CLI 기본값 \"auto\" (라이브러리 API는 undefined 유지)\n const validOcrModes = [\"auto\", \"gemini\", \"claude\", \"codex\", \"ollama\", \"off\"]\n if (opts.ocr) {\n if (!validOcrModes.includes(opts.ocr)) {\n process.stderr.write(`[kordoc] 지원하지 않는 OCR 모드: ${opts.ocr}\\n`)\n process.stderr.write(` 사용 가능: ${validOcrModes.join(\", \")}\\n`)\n process.exit(1)\n }\n parseOptions.ocrMode = opts.ocr as OcrMode\n } else {\n parseOptions.ocrMode = \"auto\"\n }\n\n // OCR 병렬 처리 수 (--ocr-jobs)\n if (opts.ocrJobs) {\n const n = parseInt(opts.ocrJobs, 10)\n if (n > 0) parseOptions.ocrConcurrency = n\n } else {\n parseOptions.ocrConcurrency = 4\n }\n\n // OCR 배치 크기 (--ocr-batch-size)\n if (opts.ocrBatchSize) {\n const n = parseInt(opts.ocrBatchSize, 10)\n if (n > 0) parseOptions.ocrBatchSize = n\n }\n\n if (!opts.silent) {\n parseOptions.onProgress = (current: number, total: number) => {\n const pct = total > 0 ? Math.round((current / total) * 100) : 0\n const filled = Math.round(pct / 5) // 20칸 진행바\n const bar = \"█\".repeat(filled) + \"░\".repeat(20 - filled)\n const cr = process.stderr.isTTY ? \"\\r\" : \"\\n\"\n process.stderr.write(`${cr}[kordoc] ${filePrefix}${fileName} — OCR: [${bar}] ${pct}% (${current}/${total}페이지)`)\n }\n }\n const result = await parse(arrayBuffer, parseOptions)\n\n if (!result.success) {\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${result.error}\\n`)\n logger.log({ level: \"error\", stage: \"finalize\", event: \"error\", message: \"파일 파싱 실패\", meta: { fileName, error: result.error, code: result.code } })\n process.exitCode = 1\n continue\n }\n\n if (!opts.silent) process.stderr.write(` OK\\n`)\n logger.log({ level: \"info\", stage: \"finalize\", event: \"done\", message: \"파일 파싱 완료\", meta: { fileName, output: opts.output ?? opts.outDir ?? \"stdout\" } })\n\n // 이미지 기반 PDF OCR 결과 표시\n if (!opts.silent && result.success && result.isImageBased) {\n process.stderr.write(` → 이미지 기반 PDF — OCR 처리됨\\n`)\n }\n\n // 경고 표시\n if (!opts.silent && result.success && result.warnings?.length) {\n for (const w of result.warnings) {\n process.stderr.write(` ⚠ ${w.message}\\n`)\n }\n }\n\n let markdown = result.markdown\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\n if (opts.outDir && result.images?.length) {\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\n }\n const output = opts.format === \"json\"\n ? JSON.stringify(result, null, 2)\n : markdown\n\n // 이미지 저장: 출력 MD 파일 기준 폴더 사용 (convert와 일치)\n const saveImages = (outFilePath: string) => {\n if (!result.images?.length) return\n const stem = basename(outFilePath).replace(/\\.[^.]+$/, \"\")\n const defaultDir = resolve(outFilePath, \"..\", stem + \"_images\")\n const imgDir = opts.imageDir ? resolve(opts.imageDir) : defaultDir\n mkdirSync(imgDir, { recursive: true })\n for (const img of result.images) {\n writeFileSync(resolve(imgDir, img.filename), img.data)\n }\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\n }\n\n if (opts.output && files.length === 1) {\n writeFileSync(opts.output, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\n saveImages(resolve(opts.output))\n } else if (opts.outDir) {\n mkdirSync(opts.outDir, { recursive: true })\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\n writeFileSync(outPath, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\n saveImages(outPath)\n } else {\n process.stdout.write(output + \"\\n\")\n saveImages(absPath) // stdout 출력 시 입력 파일 기준\n }\n } catch (err) {\n const { sanitizeError } = await import(\"./utils.js\")\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\n logger.log({ level: \"error\", stage: \"finalize\", event: \"error\", message: \"CLI 처리 중 예외\", error: { message: sanitizeError(err), name: err instanceof Error ? err.name : \"Error\", stack: err instanceof Error ? err.stack : undefined } })\n process.exitCode = 1\n }\n }\n}\n\n/** 공통 parse 옵션 등록 헬퍼 */\nfunction addParseOptions(cmd: Command): Command {\n return cmd\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\n .option(\"--image-dir <dir>\", \"이미지 저장 폴더 (기본: 입력 파일명_images 폴더)\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .option(\"--ocr <mode>\", \"OCR 모드: auto(기본), gemini, claude, codex, ollama, off\")\n .option(\"--ocr-jobs <n>\", \"OCR 병렬 처리 수 (기본: 4)\")\n .option(\"--ocr-batch-size <n>\", \"OCR 배치 크기 — CLI당 페이지 수 (기본: gemini/claude 50, codex 100)\")\n}\n\nprogram\n .enablePositionalOptions()\n .name(\"kordoc\")\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\n .version(VERSION)\n\n// `kordoc parse <files>` 서브커맨드 (권장)\naddParseOptions(\n program\n .command(\"parse\")\n .description(\"파일을 마크다운으로 파싱 (HWP, HWPX, PDF, XLSX, DOCX)\")\n .argument(\"<files...>\", \"변환할 파일 경로\")\n).action(runParse)\n\nprogram\n .command(\"convert <input>\")\n .description(\"마크다운 파일을 HWPX 또는 XLSX로 변환\")\n .option(\"-f, --format <type>\", \"출력 포맷: hwpx | xlsx\", \"hwpx\")\n .option(\"-o, --output <path>\", \"출력 파일 경로 (기본: 입력명 + 포맷 확장자)\")\n .option(\"--image-dir <dir>\", \"이미지 폴더 경로 (기본: 입력 MD 파일명_images 폴더)\")\n .option(\"--images\", \"이미지 포함 (기본: 생략, 레이아웃 문제 방지)\")\n .option(\"--template <path>\", \"HWPX 템플릿 파일 경로 (hwpx 전용)\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (input: string, opts) => {\n const validFormats = [\"hwpx\", \"xlsx\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 포맷: ${opts.format} (hwpx 또는 xlsx)\\n`)\n process.exit(1)\n }\n\n const absInput = resolve(input)\n if (!existsSync(absInput)) {\n process.stderr.write(`[kordoc] 파일을 찾을 수 없습니다: ${input}\\n`)\n process.exit(1)\n }\n\n const stem = basename(absInput).replace(/\\.[^.]+$/, \"\")\n const outPath = opts.output\n ? resolve(opts.output)\n : resolve(absInput, \"..\", `${stem}.${opts.format}`)\n\n if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absInput)} → ${basename(outPath)} ...`)\n\n try {\n const markdown = readFileSync(absInput, \"utf-8\")\n\n // 이미지 폴더에서 이미지 로드 (--images 플래그 필요)\n const imgDir = opts.imageDir ? resolve(opts.imageDir) : resolve(absInput, \"..\", stem + \"_images\")\n const images: import(\"./types.js\").ExtractedImage[] = []\n if (opts.images && existsSync(imgDir)) {\n const mimeMap: Record<string, string> = {\n png: \"image/png\", jpg: \"image/jpeg\", jpeg: \"image/jpeg\",\n gif: \"image/gif\", bmp: \"image/bmp\",\n }\n for (const entry of readdirSync(imgDir, { withFileTypes: true })) {\n if (!entry.isFile()) continue\n const fname = entry.name\n const ext = extname(fname).slice(1).toLowerCase()\n if (!mimeMap[ext]) continue\n const data = readFileSync(resolve(imgDir, fname))\n images.push({ filename: fname, data: new Uint8Array(data), mimeType: mimeMap[ext] })\n }\n if (!opts.silent) process.stderr.write(` → 이미지 ${images.length}개 로드\\n`)\n }\n\n const warnings: string[] = []\n\n let buf: ArrayBuffer\n if (opts.format === \"xlsx\") {\n if (opts.template && !opts.silent) {\n process.stderr.write(`\\n[kordoc] 경고: --template은 hwpx 전용입니다. 무시됩니다.\\n`)\n }\n buf = await markdownToXlsx(markdown, { warnings, images: images.length ? images : undefined })\n } else {\n let templateArrayBuffer: ArrayBuffer | undefined\n if (opts.template) {\n const tmplBuf = readFileSync(resolve(opts.template))\n templateArrayBuffer = tmplBuf.buffer.slice(tmplBuf.byteOffset, tmplBuf.byteOffset + tmplBuf.byteLength)\n }\n buf = await markdownToHwpx(markdown, { warnings, images: images.length ? images : undefined, templateArrayBuffer })\n }\n\n writeFileSync(outPath, Buffer.from(buf))\n\n if (!opts.silent) {\n process.stderr.write(` OK\\n`)\n process.stderr.write(` → ${outPath}\\n`)\n if (warnings.length) warnings.forEach(w => process.stderr.write(` ${w}\\n`))\n }\n } catch (err) {\n const { sanitizeError } = await import(\"./utils.js\")\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\nprogram\n .command(\"convert-pdf <input>\")\n .description(\"HWP/HWPX 파일을 PDF로 변환\")\n .option(\"-o, --output <path>\", \"출력 PDF 경로 (기본: 입력명.pdf)\")\n .option(\"-p, --pages <range>\", \"페이지 범위 (예: 1-3,5)\")\n .option(\"-q, --quality <level>\", \"출력 품질 (low|medium|high)\")\n .option(\"--timeout <ms>\", \"변환 타임아웃 (기본: 60000)\")\n .option(\"--soffice-path <path>\", \"LibreOffice 바이너리 직접 지정\")\n .option(\"--no-install\", \"LibreOffice 자동 설치 비활성화\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (input: string, opts) => {\n const absInput = resolve(input)\n if (!existsSync(absInput)) {\n process.stderr.write(`[kordoc] 파일을 찾을 수 없습니다: ${input}\\n`)\n process.exit(1)\n }\n\n const stem = basename(absInput).replace(/\\.[^.]+$/, \"\")\n const outPath = opts.output\n ? resolve(opts.output)\n : resolve(absInput, \"..\", `${stem}.pdf`)\n\n if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absInput)} → ${basename(outPath)} ...`)\n\n try {\n const result = await convertToPdf(absInput, {\n pages: opts.pages,\n timeoutMs: opts.timeout ? parseInt(opts.timeout, 10) : undefined,\n autoInstallLibreOffice: !opts.noInstall,\n sofficePath: opts.sofficePath,\n onEvent: opts.silent ? undefined : (event) => {\n switch (event.type) {\n case \"install\": {\n const { stage, percent, downloadedBytes, totalBytes } = event\n if (stage === \"download_progress\" && percent !== undefined && downloadedBytes !== undefined && totalBytes !== undefined) {\n const filled = Math.round(percent / 5)\n const bar = \"█\".repeat(filled) + \"░\".repeat(20 - filled)\n const mb = (downloadedBytes / 1024 / 1024).toFixed(1)\n const totalMb = (totalBytes / 1024 / 1024).toFixed(1)\n const cr = process.stderr.isTTY ? \"\\r\" : \"\\n\"\n process.stderr.write(`${cr}[kordoc] ${basename(absInput)} — [${bar}] ${percent}% (${mb}MB / ${totalMb}MB)`)\n } else if (stage === \"install_complete\") {\n process.stderr.write(\"\\n[kordoc] 설치 완료!\\n\")\n } else if (stage === \"install_start\") {\n process.stderr.write(`[kordoc] ${event.message}\\n`)\n }\n break\n }\n case \"convert\": {\n const { percent } = event\n if (percent !== undefined) {\n const filled = Math.round(percent / 5)\n const bar = \"█\".repeat(filled) + \"░\".repeat(20 - filled)\n const cr = process.stderr.isTTY ? \"\\r\" : \"\\n\"\n process.stderr.write(`${cr}[kordoc] ${basename(absInput)} — [${bar}] ${percent}%`)\n }\n break\n }\n case \"error\": {\n process.stderr.write(`\\n[kordoc] 오류: ${event.message}\\n`)\n if (event.suggestion) {\n process.stderr.write(` → ${event.suggestion}\\n`)\n }\n break\n }\n }\n },\n })\n\n if (!result.success) {\n if (!opts.silent) process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${result.error}\\n`)\n process.exit(1)\n }\n\n writeFileSync(outPath, result.pdf)\n\n if (!opts.silent) {\n process.stderr.write(` OK\\n`)\n process.stderr.write(` → ${outPath}\\n`)\n }\n } catch (err) {\n const { sanitizeError } = await import(\"./utils.js\")\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\nprogram\n .command(\"init-env\")\n .description(\"kordoc용 .env 템플릿 생성 (명시 실행 시에만 파일 생성)\")\n .option(\"-o, --output <path>\", \"템플릿 출력 경로\", \".env.kordoc.example\")\n .option(\"--create-dotenv\", \".env 파일이 없으면 함께 생성\")\n .option(\"--force\", \"기존 파일이 있어도 덮어쓰기\")\n .action((opts: { output: string; createDotenv?: boolean; force?: boolean }) => {\n const template = buildEnvTemplate()\n const outPath = resolve(opts.output)\n\n if (existsSync(outPath) && !opts.force) {\n process.stderr.write(`[kordoc] 이미 파일이 있습니다: ${outPath} (덮어쓰려면 --force)\\n`)\n process.exit(1)\n }\n writeFileSync(outPath, template, \"utf-8\")\n process.stderr.write(`[kordoc] 생성됨: ${outPath}\\n`)\n\n if (opts.createDotenv) {\n const dotenvPath = resolve(\".env\")\n if (existsSync(dotenvPath) && !opts.force) {\n process.stderr.write(`[kordoc] .env가 이미 있어 생성하지 않았습니다: ${dotenvPath}\\n`)\n } else {\n writeFileSync(dotenvPath, template, \"utf-8\")\n process.stderr.write(`[kordoc] 생성됨: ${dotenvPath}\\n`)\n }\n }\n })\n\nprogram\n .command(\"watch <dir>\")\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (dir: string, opts) => {\n const { watchDirectory } = await import(\"./watch.js\")\n await watchDirectory({\n dir,\n outDir: opts.outDir,\n webhook: opts.webhook,\n format: opts.format,\n pages: opts.pages,\n silent: opts.silent,\n })\n })\n\n// `kordoc <files>` 루트 커맨드 (하위 호환)\n// 주의: 서브커맨드(init-env 등)보다 뒤에서 등록해야 충돌하지 않음\naddParseOptions(\n program\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\n).action(runParse)\n\nprogram.parse()\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAEA,SAAS,cAAc,eAAe,WAAW,UAAU,YAAY,mBAAmB;AAC1F,SAAS,UAAU,SAAS,eAAe;AAE3C,SAAS,eAAe;AAMxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,SAAS,mBAA2B;AAClC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAiBA,eAAe,SAAS,OAAiB,MAAiB;AACxD,QAAM,SAAS,oBAAoB,EAAE,QAAQ,cAAc,KAAK,CAAC,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAChG,SAAO,IAAI,EAAE,OAAO,QAAQ,OAAO,UAAU,OAAO,SAAS,SAAS,0BAAgB,MAAM,EAAE,OAAO,MAAM,OAAO,EAAE,CAAC;AACrH,QAAM,eAAe,CAAC,YAAY,MAAM;AACxC,MAAI,CAAC,aAAa,SAAS,KAAK,MAAM,GAAG;AACvC,YAAQ,OAAO,MAAM,gEAAwB,KAAK,MAAM;AAAA,CAAuB;AAC/E,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,WAAS,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,UAAM,WAAW,MAAM,EAAE;AACzB,UAAM,UAAU,QAAQ,QAAQ;AAChC,UAAM,WAAW,SAAS,OAAO;AACjC,UAAM,aAAa,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,IAAI,MAAM,MAAM,OAAO;AAEvE,QAAI;AACF,YAAM,WAAW,SAAS,OAAO,EAAE;AACnC,UAAI,WAAW,MAAM,OAAO,MAAM;AAChC,gBAAQ,OAAO,MAAM;AAAA,iBAAoB,QAAQ,gEAAmB,WAAW,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,CAAO;AAC7G,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,SAAS,aAAa,OAAO;AACnC,YAAM,cAAc,cAAc,MAAM;AACxC,YAAM,SAAS,aAAa,WAAW;AAEvC,UAAI,CAAC,KAAK,QAAQ;AAChB,gBAAQ,OAAO,MAAM,YAAY,UAAU,GAAG,QAAQ,KAAK,MAAM,OAAO;AAAA,MAC1E;AACA,aAAO,IAAI,EAAE,OAAO,SAAS,OAAO,UAAU,OAAO,QAAQ,SAAS,0CAAY,MAAM,EAAE,UAAU,OAAO,EAAE,CAAC;AAE9G,YAAM,eAA6B,CAAC;AACpC,UAAI,KAAK,MAAO,cAAa,QAAQ,KAAK;AAC1C,UAAI,KAAK,iBAAiB,MAAO,cAAa,qBAAqB;AAGnE,YAAM,gBAAgB,CAAC,QAAQ,UAAU,UAAU,SAAS,UAAU,KAAK;AAC3E,UAAI,KAAK,KAAK;AACZ,YAAI,CAAC,cAAc,SAAS,KAAK,GAAG,GAAG;AACrC,kBAAQ,OAAO,MAAM,oEAA4B,KAAK,GAAG;AAAA,CAAI;AAC7D,kBAAQ,OAAO,MAAM,gCAAY,cAAc,KAAK,IAAI,CAAC;AAAA,CAAI;AAC7D,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,qBAAa,UAAU,KAAK;AAAA,MAC9B,OAAO;AACL,qBAAa,UAAU;AAAA,MACzB;AAGA,UAAI,KAAK,SAAS;AAChB,cAAM,IAAI,SAAS,KAAK,SAAS,EAAE;AACnC,YAAI,IAAI,EAAG,cAAa,iBAAiB;AAAA,MAC3C,OAAO;AACL,qBAAa,iBAAiB;AAAA,MAChC;AAGA,UAAI,KAAK,cAAc;AACrB,cAAM,IAAI,SAAS,KAAK,cAAc,EAAE;AACxC,YAAI,IAAI,EAAG,cAAa,eAAe;AAAA,MACzC;AAEA,UAAI,CAAC,KAAK,QAAQ;AAChB,qBAAa,aAAa,CAAC,SAAiB,UAAkB;AAC5D,gBAAM,MAAM,QAAQ,IAAI,KAAK,MAAO,UAAU,QAAS,GAAG,IAAI;AAC9D,gBAAM,SAAS,KAAK,MAAM,MAAM,CAAC;AACjC,gBAAM,MAAM,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,KAAK,MAAM;AACvD,gBAAM,KAAK,QAAQ,OAAO,QAAQ,OAAO;AACzC,kBAAQ,OAAO,MAAM,GAAG,EAAE,YAAY,UAAU,GAAG,QAAQ,iBAAY,GAAG,KAAK,GAAG,MAAM,OAAO,IAAI,KAAK,qBAAM;AAAA,QAChH;AAAA,MACF;AACA,YAAM,SAAS,MAAM,MAAM,aAAa,YAAY;AAEpD,UAAI,CAAC,OAAO,SAAS;AACnB,gBAAQ,OAAO,MAAM;AAAA,CAAS;AAC9B,gBAAQ,OAAO,MAAM,YAAO,OAAO,KAAK;AAAA,CAAI;AAC5C,eAAO,IAAI,EAAE,OAAO,SAAS,OAAO,YAAY,OAAO,SAAS,SAAS,0CAAY,MAAM,EAAE,UAAU,OAAO,OAAO,OAAO,MAAM,OAAO,KAAK,EAAE,CAAC;AACjJ,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM;AAAA,CAAO;AAC9C,aAAO,IAAI,EAAE,OAAO,QAAQ,OAAO,YAAY,OAAO,QAAQ,SAAS,0CAAY,MAAM,EAAE,UAAU,QAAQ,KAAK,UAAU,KAAK,UAAU,SAAS,EAAE,CAAC;AAGvJ,UAAI,CAAC,KAAK,UAAU,OAAO,WAAW,OAAO,cAAc;AACzD,gBAAQ,OAAO,MAAM;AAAA,CAA4B;AAAA,MACnD;AAGA,UAAI,CAAC,KAAK,UAAU,OAAO,WAAW,OAAO,UAAU,QAAQ;AAC7D,mBAAW,KAAK,OAAO,UAAU;AAC/B,kBAAQ,OAAO,MAAM,YAAO,EAAE,OAAO;AAAA,CAAI;AAAA,QAC3C;AAAA,MACF;AAEA,UAAI,WAAW,OAAO;AAEtB,UAAI,KAAK,UAAU,OAAO,QAAQ,QAAQ;AACxC,mBAAW,SAAS,QAAQ,uBAAuB,wBAAwB;AAAA,MAC7E;AACA,YAAM,SAAS,KAAK,WAAW,SAC3B,KAAK,UAAU,QAAQ,MAAM,CAAC,IAC9B;AAGJ,YAAM,aAAa,CAAC,gBAAwB;AAC1C,YAAI,CAAC,OAAO,QAAQ,OAAQ;AAC5B,cAAM,OAAO,SAAS,WAAW,EAAE,QAAQ,YAAY,EAAE;AACzD,cAAM,aAAa,QAAQ,aAAa,MAAM,OAAO,SAAS;AAC9D,cAAM,SAAS,KAAK,WAAW,QAAQ,KAAK,QAAQ,IAAI;AACxD,kBAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACrC,mBAAW,OAAO,OAAO,QAAQ;AAC/B,wBAAc,QAAQ,QAAQ,IAAI,QAAQ,GAAG,IAAI,IAAI;AAAA,QACvD;AACA,YAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,YAAO,OAAO,OAAO,MAAM,oCAAW,MAAM;AAAA,CAAI;AAAA,MACzF;AAEA,UAAI,KAAK,UAAU,MAAM,WAAW,GAAG;AACrC,sBAAc,KAAK,QAAQ,QAAQ,OAAO;AAC1C,YAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,YAAO,KAAK,MAAM;AAAA,CAAI;AAC7D,mBAAW,QAAQ,KAAK,MAAM,CAAC;AAAA,MACjC,WAAW,KAAK,QAAQ;AACtB,kBAAU,KAAK,QAAQ,EAAE,WAAW,KAAK,CAAC;AAC1C,cAAM,SAAS,KAAK,WAAW,SAAS,UAAU;AAClD,cAAM,UAAU,QAAQ,KAAK,QAAQ,SAAS,QAAQ,YAAY,MAAM,CAAC;AACzE,sBAAc,SAAS,QAAQ,OAAO;AACtC,YAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,YAAO,OAAO;AAAA,CAAI;AACzD,mBAAW,OAAO;AAAA,MACpB,OAAO;AACL,gBAAQ,OAAO,MAAM,SAAS,IAAI;AAClC,mBAAW,OAAO;AAAA,MACpB;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,qBAAY;AACnD,cAAQ,OAAO,MAAM;AAAA,kBAAqB,QAAQ,WAAM,cAAc,GAAG,CAAC;AAAA,CAAI;AAC9E,aAAO,IAAI,EAAE,OAAO,SAAS,OAAO,YAAY,OAAO,SAAS,SAAS,wCAAe,OAAO,EAAE,SAAS,cAAc,GAAG,GAAG,MAAM,eAAe,QAAQ,IAAI,OAAO,SAAS,OAAO,eAAe,QAAQ,IAAI,QAAQ,OAAU,EAAE,CAAC;AACtO,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AACF;AAGA,SAAS,gBAAgB,KAAuB;AAC9C,SAAO,IACJ,OAAO,uBAAuB,2EAAoB,EAClD,OAAO,uBAAuB,0EAAmB,EACjD,OAAO,uBAAuB,mEAA2B,EACzD,OAAO,mBAAmB,wEAAgC,UAAU,EACpE,OAAO,sBAAsB,qEAAmB,EAChD,OAAO,qBAAqB,kHAAkC,EAC9D,OAAO,YAAY,oDAAY,EAC/B,OAAO,gBAAgB,0EAAsD,EAC7E,OAAO,kBAAkB,wDAAqB,EAC9C,OAAO,wBAAwB,sHAA0D;AAC9F;AAEA,QACG,wBAAwB,EACxB,KAAK,QAAQ,EACb,YAAY,2GAAoD,EAChE,QAAQ,OAAO;AAGlB;AAAA,EACE,QACG,QAAQ,OAAO,EACf,YAAY,mGAA4C,EACxD,SAAS,cAAc,8CAAW;AACvC,EAAE,OAAO,QAAQ;AAEjB,QACG,QAAQ,iBAAiB,EACzB,YAAY,uFAA2B,EACvC,OAAO,uBAAuB,0CAAsB,MAAM,EAC1D,OAAO,uBAAuB,6GAA6B,EAC3D,OAAO,qBAAqB,qHAAqC,EACjE,OAAO,YAAY,kHAA6B,EAChD,OAAO,qBAAqB,uEAA0B,EACtD,OAAO,YAAY,oDAAY,EAC/B,OAAO,OAAO,OAAe,SAAS;AACrC,QAAM,eAAe,CAAC,QAAQ,MAAM;AACpC,MAAI,CAAC,aAAa,SAAS,KAAK,MAAM,GAAG;AACvC,YAAQ,OAAO,MAAM,gEAAwB,KAAK,MAAM;AAAA,CAAmB;AAC3E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,QAAQ,KAAK;AAC9B,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,YAAQ,OAAO,MAAM,6EAA2B,KAAK;AAAA,CAAI;AACzD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,SAAS,QAAQ,EAAE,QAAQ,YAAY,EAAE;AACtD,QAAM,UAAU,KAAK,SACjB,QAAQ,KAAK,MAAM,IACnB,QAAQ,UAAU,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,EAAE;AAEpD,MAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,YAAY,SAAS,QAAQ,CAAC,WAAM,SAAS,OAAO,CAAC,MAAM;AAElG,MAAI;AACF,UAAM,WAAW,aAAa,UAAU,OAAO;AAG/C,UAAM,SAAS,KAAK,WAAW,QAAQ,KAAK,QAAQ,IAAI,QAAQ,UAAU,MAAM,OAAO,SAAS;AAChG,UAAM,SAAgD,CAAC;AACvD,QAAI,KAAK,UAAU,WAAW,MAAM,GAAG;AACrC,YAAM,UAAkC;AAAA,QACtC,KAAK;AAAA,QAAa,KAAK;AAAA,QAAc,MAAM;AAAA,QAC3C,KAAK;AAAA,QAAa,KAAK;AAAA,MACzB;AACA,iBAAW,SAAS,YAAY,QAAQ,EAAE,eAAe,KAAK,CAAC,GAAG;AAChE,YAAI,CAAC,MAAM,OAAO,EAAG;AACrB,cAAM,QAAQ,MAAM;AACpB,cAAM,MAAM,QAAQ,KAAK,EAAE,MAAM,CAAC,EAAE,YAAY;AAChD,YAAI,CAAC,QAAQ,GAAG,EAAG;AACnB,cAAM,OAAO,aAAa,QAAQ,QAAQ,KAAK,CAAC;AAChD,eAAO,KAAK,EAAE,UAAU,OAAO,MAAM,IAAI,WAAW,IAAI,GAAG,UAAU,QAAQ,GAAG,EAAE,CAAC;AAAA,MACrF;AACA,UAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,+BAAW,OAAO,MAAM;AAAA,CAAQ;AAAA,IACzE;AAEA,UAAM,WAAqB,CAAC;AAE5B,QAAI;AACJ,QAAI,KAAK,WAAW,QAAQ;AAC1B,UAAI,KAAK,YAAY,CAAC,KAAK,QAAQ;AACjC,gBAAQ,OAAO,MAAM;AAAA;AAAA,CAAiD;AAAA,MACxE;AACA,YAAM,MAAM,eAAe,UAAU,EAAE,UAAU,QAAQ,OAAO,SAAS,SAAS,OAAU,CAAC;AAAA,IAC/F,OAAO;AACL,UAAI;AACJ,UAAI,KAAK,UAAU;AACjB,cAAM,UAAU,aAAa,QAAQ,KAAK,QAAQ,CAAC;AACnD,8BAAsB,QAAQ,OAAO,MAAM,QAAQ,YAAY,QAAQ,aAAa,QAAQ,UAAU;AAAA,MACxG;AACA,YAAM,MAAM,eAAe,UAAU,EAAE,UAAU,QAAQ,OAAO,SAAS,SAAS,QAAW,oBAAoB,CAAC;AAAA,IACpH;AAEA,kBAAc,SAAS,OAAO,KAAK,GAAG,CAAC;AAEvC,QAAI,CAAC,KAAK,QAAQ;AAChB,cAAQ,OAAO,MAAM;AAAA,CAAO;AAC5B,cAAQ,OAAO,MAAM,YAAO,OAAO;AAAA,CAAI;AACvC,UAAI,SAAS,OAAQ,UAAS,QAAQ,OAAK,QAAQ,OAAO,MAAM,KAAK,CAAC;AAAA,CAAI,CAAC;AAAA,IAC7E;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,qBAAY;AACnD,YAAQ,OAAO,MAAM;AAAA,CAAS;AAC9B,YAAQ,OAAO,MAAM,YAAO,cAAc,GAAG,CAAC;AAAA,CAAI;AAClD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,qBAAqB,EAC7B,YAAY,oDAAsB,EAClC,OAAO,uBAAuB,sEAAyB,EACvD,OAAO,uBAAuB,iDAAmB,EACjD,OAAO,yBAAyB,6CAAyB,EACzD,OAAO,kBAAkB,6DAAqB,EAC9C,OAAO,yBAAyB,gEAAwB,EACxD,OAAO,gBAAgB,gEAAwB,EAC/C,OAAO,YAAY,oDAAY,EAC/B,OAAO,OAAO,OAAe,SAAS;AACrC,QAAM,WAAW,QAAQ,KAAK;AAC9B,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,YAAQ,OAAO,MAAM,6EAA2B,KAAK;AAAA,CAAI;AACzD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,SAAS,QAAQ,EAAE,QAAQ,YAAY,EAAE;AACtD,QAAM,UAAU,KAAK,SACjB,QAAQ,KAAK,MAAM,IACnB,QAAQ,UAAU,MAAM,GAAG,IAAI,MAAM;AAEzC,MAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM,YAAY,SAAS,QAAQ,CAAC,WAAM,SAAS,OAAO,CAAC,MAAM;AAElG,MAAI;AACF,UAAM,SAAS,MAAM,aAAa,UAAU;AAAA,MAC1C,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,UAAU,SAAS,KAAK,SAAS,EAAE,IAAI;AAAA,MACvD,wBAAwB,CAAC,KAAK;AAAA,MAC9B,aAAa,KAAK;AAAA,MAClB,SAAS,KAAK,SAAS,SAAY,CAAC,UAAU;AAC5C,gBAAQ,MAAM,MAAM;AAAA,UAClB,KAAK,WAAW;AACd,kBAAM,EAAE,OAAO,SAAS,iBAAiB,WAAW,IAAI;AACxD,gBAAI,UAAU,uBAAuB,YAAY,UAAa,oBAAoB,UAAa,eAAe,QAAW;AACvH,oBAAM,SAAS,KAAK,MAAM,UAAU,CAAC;AACrC,oBAAM,MAAM,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,KAAK,MAAM;AACvD,oBAAM,MAAM,kBAAkB,OAAO,MAAM,QAAQ,CAAC;AACpD,oBAAM,WAAW,aAAa,OAAO,MAAM,QAAQ,CAAC;AACpD,oBAAM,KAAK,QAAQ,OAAO,QAAQ,OAAO;AACzC,sBAAQ,OAAO,MAAM,GAAG,EAAE,YAAY,SAAS,QAAQ,CAAC,YAAO,GAAG,KAAK,OAAO,MAAM,EAAE,QAAQ,OAAO,KAAK;AAAA,YAC5G,WAAW,UAAU,oBAAoB;AACvC,sBAAQ,OAAO,MAAM,yCAAqB;AAAA,YAC5C,WAAW,UAAU,iBAAiB;AACpC,sBAAQ,OAAO,MAAM,YAAY,MAAM,OAAO;AAAA,CAAI;AAAA,YACpD;AACA;AAAA,UACF;AAAA,UACA,KAAK,WAAW;AACd,kBAAM,EAAE,QAAQ,IAAI;AACpB,gBAAI,YAAY,QAAW;AACzB,oBAAM,SAAS,KAAK,MAAM,UAAU,CAAC;AACrC,oBAAM,MAAM,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,KAAK,MAAM;AACvD,oBAAM,KAAK,QAAQ,OAAO,QAAQ,OAAO;AACzC,sBAAQ,OAAO,MAAM,GAAG,EAAE,YAAY,SAAS,QAAQ,CAAC,YAAO,GAAG,KAAK,OAAO,GAAG;AAAA,YACnF;AACA;AAAA,UACF;AAAA,UACA,KAAK,SAAS;AACZ,oBAAQ,OAAO,MAAM;AAAA,yBAAkB,MAAM,OAAO;AAAA,CAAI;AACxD,gBAAI,MAAM,YAAY;AACpB,sBAAQ,OAAO,MAAM,YAAO,MAAM,UAAU;AAAA,CAAI;AAAA,YAClD;AACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI,CAAC,OAAO,SAAS;AACnB,UAAI,CAAC,KAAK,OAAQ,SAAQ,OAAO,MAAM;AAAA,CAAS;AAChD,cAAQ,OAAO,MAAM,YAAO,OAAO,KAAK;AAAA,CAAI;AAC5C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,kBAAc,SAAS,OAAO,GAAG;AAEjC,QAAI,CAAC,KAAK,QAAQ;AAChB,cAAQ,OAAO,MAAM;AAAA,CAAO;AAC5B,cAAQ,OAAO,MAAM,YAAO,OAAO;AAAA,CAAI;AAAA,IACzC;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,qBAAY;AACnD,YAAQ,OAAO,MAAM;AAAA,CAAS;AAC9B,YAAQ,OAAO,MAAM,YAAO,cAAc,GAAG,CAAC;AAAA,CAAI;AAClD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,YAAY,4HAAuC,EACnD,OAAO,uBAAuB,gDAAa,qBAAqB,EAChE,OAAO,mBAAmB,sEAAoB,EAC9C,OAAO,WAAW,6EAAiB,EACnC,OAAO,CAAC,SAAsE;AAC7E,QAAM,WAAW,iBAAiB;AAClC,QAAM,UAAU,QAAQ,KAAK,MAAM;AAEnC,MAAI,WAAW,OAAO,KAAK,CAAC,KAAK,OAAO;AACtC,YAAQ,OAAO,MAAM,sEAAyB,OAAO;AAAA,CAAoB;AACzE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,gBAAc,SAAS,UAAU,OAAO;AACxC,UAAQ,OAAO,MAAM,gCAAiB,OAAO;AAAA,CAAI;AAEjD,MAAI,KAAK,cAAc;AACrB,UAAM,aAAa,QAAQ,MAAM;AACjC,QAAI,WAAW,UAAU,KAAK,CAAC,KAAK,OAAO;AACzC,cAAQ,OAAO,MAAM,0GAAoC,UAAU;AAAA,CAAI;AAAA,IACzE,OAAO;AACL,oBAAc,YAAY,UAAU,OAAO;AAC3C,cAAQ,OAAO,MAAM,gCAAiB,UAAU;AAAA,CAAI;AAAA,IACtD;AAAA,EACF;AACF,CAAC;AAEH,QACG,QAAQ,aAAa,EACrB,YAAY,4FAAsB,EAClC,OAAO,mBAAmB,4CAAc,EACxC,OAAO,uBAAuB,iEAAe,EAC7C,OAAO,uBAAuB,8CAAW,EACzC,OAAO,mBAAmB,yDAA2B,UAAU,EAC/D,OAAO,YAAY,oDAAY,EAC/B,OAAO,OAAO,KAAa,SAAS;AACnC,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,qBAAY;AACpD,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK;AAAA,IACd,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ,QAAQ,KAAK;AAAA,EACf,CAAC;AACH,CAAC;AAIH;AAAA,EACE,QACG,SAAS,cAAc,2EAAwC;AACpE,EAAE,OAAO,QAAQ;AAEjB,QAAQ,MAAM;","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["/** kordoc CLI — 모두 파싱해버리겠다 */\n\nimport { readFileSync, writeFileSync, mkdirSync, statSync, existsSync, readdirSync } from \"fs\"\nimport { basename, resolve, extname } from \"path\"\nimport { cpus } from \"os\"\nimport { Command } from \"commander\"\nimport { parse, detectFormat, markdownToHwpx, markdownToXlsx } from \"./index.js\"\nimport type { ParseOptions, OcrMode } from \"./types.js\"\nimport { VERSION, toArrayBuffer } from \"./utils.js\"\nimport { createLoggerFromEnv, generateRunId } from \"./logging/logger.js\"\n\nconst program = new Command()\n\nfunction buildEnvTemplate(): string {\n return [\n \"# kordoc 환경 변수 템플릿\",\n \"# 필요 항목만 주석 해제해서 사용하세요.\",\n \"\",\n \"# ---------- OCR ----------\",\n \"# CLI OCR 모드에서 Ollama 모델 지정\",\n \"# KORDOC_OLLAMA_MODEL=qwen3-vl:8b\",\n \"\",\n \"# Unified OCR 파이프라인 모델 기본값\",\n \"KORDOC_UNIFIED_MODEL_CANDIDATES=mistralai/mistral-medium-3-instruct,moonshotai/kimi-k2.5,qwen/qwen3.5-122b-a10b,qwen/qwen3.5-397b-a17b,moonshotai/kimi-k2-thinking,moonshotai/kimi-k2-instruct,moonshotai/kimi-k2-instruct-0905\",\n \"KORDOC_UNIFIED_MODEL_MAX_TOKENS={\\\"mistralai/mistral-medium-3-instruct\\\":8192,\\\"moonshotai/kimi-k2.5\\\":64000,\\\"qwen/qwen3.5-122b-a10b\\\":64000,\\\"qwen/qwen3.5-397b-a17b\\\":64000,\\\"moonshotai/kimi-k2-thinking\\\":64000,\\\"moonshotai/kimi-k2-instruct\\\":64000,\\\"moonshotai/kimi-k2-instruct-0905\\\":64000}\",\n \"\",\n \"# NIM API 키 (단일/다중)\",\n \"NVIDIA_API_KEY=\",\n \"# NVIDIA_API_KEYS=nvapi-key1,nvapi-key2,nvapi-key3\",\n \"\",\n \"# ---------- 로깅 ----------\",\n \"# KORDOC_LOG_LEVEL=error\",\n \"# KORDOC_LOG_FILE=./logs/kordoc.jsonl\",\n \"# KORDOC_LOG_STACK=0\",\n \"# KORDOC_LOG_PROGRESS_SAMPLE_MS=1000\",\n \"# KORDOC_LOG_BASENAME_PATHS=1\",\n \"# KORDOC_LOG_TEXT_LIMIT=400\",\n \"\",\n ].join(\"\\n\")\n}\n\n/** 공통 parse 옵션 타입 */\ninterface ParseOpts {\n output?: string\n outDir?: string\n pages?: string\n format: string\n headerFooter: boolean\n imageDir?: string\n silent?: boolean\n ocr?: string\n ocrJobs?: string\n ocrBatchSize?: string\n}\n\n/** parse 액션 공통 구현 — 루트 커맨드와 `parse` 서브커맨드가 공유 */\nasync function runParse(files: string[], opts: ParseOpts) {\n const logger = createLoggerFromEnv().withRun(generateRunId(\"cli\")).child({ component: \"cli.ts\" })\n logger.log({ level: \"info\", stage: \"detect\", event: \"start\", message: \"CLI parse 시작\", meta: { files: files.length } })\n const validFormats = [\"markdown\", \"json\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 형식: ${opts.format} (markdown 또는 json)\\n`)\n process.exit(1)\n }\n for (let fi = 0; fi < files.length; fi++) {\n const filePath = files[fi]\n const absPath = resolve(filePath)\n const fileName = basename(absPath)\n const filePrefix = files.length > 1 ? `[${fi + 1}/${files.length}] ` : \"\"\n\n try {\n const fileSize = statSync(absPath).size\n if (fileSize > 500 * 1024 * 1024) {\n process.stderr.write(`\\n[kordoc] SKIP: ${fileName} — 파일이 너무 큽니다 (${(fileSize / 1024 / 1024).toFixed(1)}MB)\\n`)\n process.exitCode = 1\n continue\n }\n const buffer = readFileSync(absPath)\n const arrayBuffer = toArrayBuffer(buffer)\n const format = detectFormat(arrayBuffer)\n\n if (!opts.silent) {\n process.stderr.write(`[kordoc] ${filePrefix}${fileName} (${format}) ...`)\n }\n logger.log({ level: \"debug\", stage: \"detect\", event: \"done\", message: \"파일 포맷 감지\", meta: { fileName, format } })\n\n const parseOptions: ParseOptions = {}\n if (opts.pages) parseOptions.pages = opts.pages as string\n if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false\n\n // OCR 모드: CLI 기본값 \"auto\" (라이브러리 API는 undefined 유지)\n const validOcrModes = [\"auto\", \"gemini\", \"claude\", \"codex\", \"ollama\", \"off\"]\n if (opts.ocr) {\n if (!validOcrModes.includes(opts.ocr)) {\n process.stderr.write(`[kordoc] 지원하지 않는 OCR 모드: ${opts.ocr}\\n`)\n process.stderr.write(` 사용 가능: ${validOcrModes.join(\", \")}\\n`)\n process.exit(1)\n }\n parseOptions.ocrMode = opts.ocr as OcrMode\n } else {\n parseOptions.ocrMode = \"auto\"\n }\n\n // OCR 병렬 처리 수 (--ocr-jobs)\n if (opts.ocrJobs) {\n const n = parseInt(opts.ocrJobs, 10)\n if (n > 0) parseOptions.ocrConcurrency = n\n } else {\n parseOptions.ocrConcurrency = 4\n }\n\n // OCR 배치 크기 (--ocr-batch-size)\n if (opts.ocrBatchSize) {\n const n = parseInt(opts.ocrBatchSize, 10)\n if (n > 0) parseOptions.ocrBatchSize = n\n }\n\n if (!opts.silent) {\n parseOptions.onProgress = (current: number, total: number) => {\n const pct = total > 0 ? Math.round((current / total) * 100) : 0\n const filled = Math.round(pct / 5) // 20칸 진행바\n const bar = \"█\".repeat(filled) + \"░\".repeat(20 - filled)\n const cr = process.stderr.isTTY ? \"\\r\" : \"\\n\"\n process.stderr.write(`${cr}[kordoc] ${filePrefix}${fileName} — OCR: [${bar}] ${pct}% (${current}/${total}페이지)`)\n }\n }\n const result = await parse(arrayBuffer, parseOptions)\n\n if (!result.success) {\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${result.error}\\n`)\n logger.log({ level: \"error\", stage: \"finalize\", event: \"error\", message: \"파일 파싱 실패\", meta: { fileName, error: result.error, code: result.code } })\n process.exitCode = 1\n continue\n }\n\n if (!opts.silent) process.stderr.write(` OK\\n`)\n logger.log({ level: \"info\", stage: \"finalize\", event: \"done\", message: \"파일 파싱 완료\", meta: { fileName, output: opts.output ?? opts.outDir ?? \"stdout\" } })\n\n // 이미지 기반 PDF OCR 결과 표시\n if (!opts.silent && result.success && result.isImageBased) {\n process.stderr.write(` → 이미지 기반 PDF — OCR 처리됨\\n`)\n }\n\n // 경고 표시\n if (!opts.silent && result.success && result.warnings?.length) {\n for (const w of result.warnings) {\n process.stderr.write(` ⚠ ${w.message}\\n`)\n }\n }\n\n let markdown = result.markdown\n // --out-dir 시 이미지 참조 경로에 images/ 접두사 추가\n if (opts.outDir && result.images?.length) {\n markdown = markdown.replace(/!\\[image\\]\\(image_/g, \"![image](images/image_\")\n }\n const output = opts.format === \"json\"\n ? JSON.stringify(result, null, 2)\n : markdown\n\n // 이미지 저장: 출력 MD 파일 기준 폴더 사용 (convert와 일치)\n const saveImages = (outFilePath: string) => {\n if (!result.images?.length) return\n const stem = basename(outFilePath).replace(/\\.[^.]+$/, \"\")\n const defaultDir = resolve(outFilePath, \"..\", stem + \"_images\")\n const imgDir = opts.imageDir ? resolve(opts.imageDir) : defaultDir\n mkdirSync(imgDir, { recursive: true })\n for (const img of result.images) {\n writeFileSync(resolve(imgDir, img.filename), img.data)\n }\n if (!opts.silent) process.stderr.write(` → ${result.images.length}개 이미지 → ${imgDir}\\n`)\n }\n\n if (opts.output && files.length === 1) {\n writeFileSync(opts.output, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${opts.output}\\n`)\n saveImages(resolve(opts.output))\n } else if (opts.outDir) {\n mkdirSync(opts.outDir, { recursive: true })\n const outExt = opts.format === \"json\" ? \".json\" : \".md\"\n const outPath = resolve(opts.outDir, fileName.replace(/\\.[^.]+$/, outExt))\n writeFileSync(outPath, output, \"utf-8\")\n if (!opts.silent) process.stderr.write(` → ${outPath}\\n`)\n saveImages(outPath)\n } else {\n process.stdout.write(output + \"\\n\")\n saveImages(absPath) // stdout 출력 시 입력 파일 기준\n }\n } catch (err) {\n const { sanitizeError } = await import(\"./utils.js\")\n process.stderr.write(`\\n[kordoc] ERROR: ${fileName} — ${sanitizeError(err)}\\n`)\n logger.log({ level: \"error\", stage: \"finalize\", event: \"error\", message: \"CLI 처리 중 예외\", error: { message: sanitizeError(err), name: err instanceof Error ? err.name : \"Error\", stack: err instanceof Error ? err.stack : undefined } })\n process.exitCode = 1\n }\n }\n}\n\n/** 공통 parse 옵션 등록 헬퍼 */\nfunction addParseOptions(cmd: Command): Command {\n return cmd\n .option(\"-o, --output <path>\", \"출력 파일 경로 (단일 파일 시)\")\n .option(\"-d, --out-dir <dir>\", \"출력 디렉토리 (다중 파일 시)\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위 (예: 1-3, 1,3,5)\")\n .option(\"--format <type>\", \"출력 형식: markdown (기본) 또는 json\", \"markdown\")\n .option(\"--no-header-footer\", \"PDF 머리글/바닥글 자동 제거\")\n .option(\"--image-dir <dir>\", \"이미지 저장 폴더 (기본: 입력 파일명_images 폴더)\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .option(\"--ocr <mode>\", \"OCR 모드: auto(기본), gemini, claude, codex, ollama, off\")\n .option(\"--ocr-jobs <n>\", \"OCR 병렬 처리 수 (기본: 4)\")\n .option(\"--ocr-batch-size <n>\", \"OCR 배치 크기 — CLI당 페이지 수 (기본: gemini/claude 50, codex 100)\")\n}\n\nprogram\n .enablePositionalOptions()\n .name(\"kordoc\")\n .description(\"모두 파싱해버리겠다 — HWP, HWPX, PDF, XLSX, DOCX → Markdown\")\n .version(VERSION)\n\n// `kordoc parse <files>` 서브커맨드 (권장)\naddParseOptions(\n program\n .command(\"parse\")\n .description(\"파일을 마크다운으로 파싱 (HWP, HWPX, PDF, XLSX, DOCX)\")\n .argument(\"<files...>\", \"변환할 파일 경로\")\n).action(runParse)\n\nprogram\n .command(\"convert <input>\")\n .description(\"마크다운 파일을 HWPX 또는 XLSX로 변환\")\n .option(\"-f, --format <type>\", \"출력 포맷: hwpx | xlsx\", \"hwpx\")\n .option(\"-o, --output <path>\", \"출력 파일 경로 (기본: 입력명 + 포맷 확장자)\")\n .option(\"--image-dir <dir>\", \"이미지 폴더 경로 (기본: 입력 MD 파일명_images 폴더)\")\n .option(\"--images\", \"이미지 포함 (기본: 생략, 레이아웃 문제 방지)\")\n .option(\"--template <path>\", \"HWPX 템플릿 파일 경로 (hwpx 전용)\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (input: string, opts) => {\n const validFormats = [\"hwpx\", \"xlsx\"]\n if (!validFormats.includes(opts.format)) {\n process.stderr.write(`[kordoc] 지원하지 않는 포맷: ${opts.format} (hwpx 또는 xlsx)\\n`)\n process.exit(1)\n }\n\n const absInput = resolve(input)\n if (!existsSync(absInput)) {\n process.stderr.write(`[kordoc] 파일을 찾을 수 없습니다: ${input}\\n`)\n process.exit(1)\n }\n\n const stem = basename(absInput).replace(/\\.[^.]+$/, \"\")\n const outPath = opts.output\n ? resolve(opts.output)\n : resolve(absInput, \"..\", `${stem}.${opts.format}`)\n\n if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absInput)} → ${basename(outPath)} ...`)\n\n try {\n const markdown = readFileSync(absInput, \"utf-8\")\n\n // 이미지 폴더에서 이미지 로드 (--images 플래그 필요)\n const imgDir = opts.imageDir ? resolve(opts.imageDir) : resolve(absInput, \"..\", stem + \"_images\")\n const images: import(\"./types.js\").ExtractedImage[] = []\n if (opts.images && existsSync(imgDir)) {\n const mimeMap: Record<string, string> = {\n png: \"image/png\", jpg: \"image/jpeg\", jpeg: \"image/jpeg\",\n gif: \"image/gif\", bmp: \"image/bmp\",\n }\n for (const entry of readdirSync(imgDir, { withFileTypes: true })) {\n if (!entry.isFile()) continue\n const fname = entry.name\n const ext = extname(fname).slice(1).toLowerCase()\n if (!mimeMap[ext]) continue\n const data = readFileSync(resolve(imgDir, fname))\n images.push({ filename: fname, data: new Uint8Array(data), mimeType: mimeMap[ext] })\n }\n if (!opts.silent) process.stderr.write(` → 이미지 ${images.length}개 로드\\n`)\n }\n\n const warnings: string[] = []\n\n let buf: ArrayBuffer\n if (opts.format === \"xlsx\") {\n if (opts.template && !opts.silent) {\n process.stderr.write(`\\n[kordoc] 경고: --template은 hwpx 전용입니다. 무시됩니다.\\n`)\n }\n buf = await markdownToXlsx(markdown, { warnings, images: images.length ? images : undefined })\n } else {\n let templateArrayBuffer: ArrayBuffer | undefined\n if (opts.template) {\n const tmplBuf = readFileSync(resolve(opts.template))\n templateArrayBuffer = tmplBuf.buffer.slice(tmplBuf.byteOffset, tmplBuf.byteOffset + tmplBuf.byteLength)\n }\n buf = await markdownToHwpx(markdown, { warnings, images: images.length ? images : undefined, templateArrayBuffer })\n }\n\n writeFileSync(outPath, Buffer.from(buf))\n\n if (!opts.silent) {\n process.stderr.write(` OK\\n`)\n process.stderr.write(` → ${outPath}\\n`)\n if (warnings.length) warnings.forEach(w => process.stderr.write(` ${w}\\n`))\n }\n } catch (err) {\n const { sanitizeError } = await import(\"./utils.js\")\n process.stderr.write(` FAIL\\n`)\n process.stderr.write(` → ${sanitizeError(err)}\\n`)\n process.exit(1)\n }\n })\n\nprogram\n .command(\"init-env\")\n .description(\"kordoc용 .env 템플릿 생성 (명시 실행 시에만 파일 생성)\")\n .option(\"-o, --output <path>\", \"템플릿 출력 경로\", \".env.kordoc.example\")\n .option(\"--create-dotenv\", \".env 파일이 없으면 함께 생성\")\n .option(\"--force\", \"기존 파일이 있어도 덮어쓰기\")\n .action((opts: { output: string; createDotenv?: boolean; force?: boolean }) => {\n const template = buildEnvTemplate()\n const outPath = resolve(opts.output)\n\n if (existsSync(outPath) && !opts.force) {\n process.stderr.write(`[kordoc] 이미 파일이 있습니다: ${outPath} (덮어쓰려면 --force)\\n`)\n process.exit(1)\n }\n writeFileSync(outPath, template, \"utf-8\")\n process.stderr.write(`[kordoc] 생성됨: ${outPath}\\n`)\n\n if (opts.createDotenv) {\n const dotenvPath = resolve(\".env\")\n if (existsSync(dotenvPath) && !opts.force) {\n process.stderr.write(`[kordoc] .env가 이미 있어 생성하지 않았습니다: ${dotenvPath}\\n`)\n } else {\n writeFileSync(dotenvPath, template, \"utf-8\")\n process.stderr.write(`[kordoc] 생성됨: ${dotenvPath}\\n`)\n }\n }\n })\n\nprogram\n .command(\"watch <dir>\")\n .description(\"디렉토리 감시 — 새 문서 자동 변환\")\n .option(\"--webhook <url>\", \"결과 전송 웹훅 URL\")\n .option(\"-d, --out-dir <dir>\", \"변환 결과 출력 디렉토리\")\n .option(\"-p, --pages <range>\", \"페이지/섹션 범위\")\n .option(\"--format <type>\", \"출력 형식: markdown 또는 json\", \"markdown\")\n .option(\"--silent\", \"진행 메시지 숨기기\")\n .action(async (dir: string, opts) => {\n const { watchDirectory } = await import(\"./watch.js\")\n await watchDirectory({\n dir,\n outDir: opts.outDir,\n webhook: opts.webhook,\n format: opts.format,\n pages: opts.pages,\n silent: opts.silent,\n })\n })\n\n// `kordoc <files>` 루트 커맨드 (하위 호환)\n// 주의: 서브커맨드(init-env 등)보다 뒤에서 등록해야 충돌하지 않음\naddParseOptions(\n program\n .argument(\"<files...>\", \"변환할 파일 경로 (HWP, HWPX, PDF, XLSX, DOCX)\")\n).action(runParse)\n\nprogram.parse()\n"],"mappings":"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","names":[]}