@clazic/kordoc 2.4.18 → 2.5.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.
Files changed (49) hide show
  1. package/dist/{auto-detect-2YGFYQCN.js → auto-detect-CBYICI6B.js} +4 -4
  2. package/dist/chunk-AEWWERJ5.js +35 -0
  3. package/dist/chunk-AEWWERJ5.js.map +1 -0
  4. package/dist/{chunk-7NOZFYH6.js → chunk-CLK4PNZ7.js} +7 -8
  5. package/dist/chunk-CLK4PNZ7.js.map +1 -0
  6. package/dist/chunk-CPTOBJJD.js +125 -0
  7. package/dist/chunk-CPTOBJJD.js.map +1 -0
  8. package/dist/{chunk-W2KDIKDF.js → chunk-IJGNPAK2.js} +2 -2
  9. package/dist/{chunk-W2KDIKDF.js.map → chunk-IJGNPAK2.js.map} +1 -1
  10. package/dist/{chunk-T7EBS5XP.js → chunk-NKUNXGWI.js} +10 -22
  11. package/dist/chunk-NKUNXGWI.js.map +1 -0
  12. package/dist/chunk-THBLCND6.js +33 -0
  13. package/dist/chunk-THBLCND6.js.map +1 -0
  14. package/dist/{chunk-34WIGIQC.js → chunk-Y4WFKJ5P.js} +1 -1
  15. package/dist/chunk-Y4WFKJ5P.js.map +1 -0
  16. package/dist/cli.js +41 -13
  17. package/dist/cli.js.map +1 -1
  18. package/dist/doctor-SJ7NYSXC.js +126 -0
  19. package/dist/doctor-SJ7NYSXC.js.map +1 -0
  20. package/dist/index.cjs +2606 -186
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +6 -7
  23. package/dist/index.d.ts +6 -7
  24. package/dist/index.js +2619 -197
  25. package/dist/index.js.map +1 -1
  26. package/dist/install-commands-P2KTEXQ4.js +11 -0
  27. package/dist/mcp.js +8 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/pm-7KGLH6MX.js +9 -0
  30. package/dist/{resolve-673XFZQ6.js → resolve-XWYJYKKH.js} +15 -36
  31. package/dist/resolve-XWYJYKKH.js.map +1 -0
  32. package/dist/setup/doctor.cjs +308 -0
  33. package/dist/setup/doctor.js +288 -0
  34. package/dist/{utils-DHOODYKU.js → utils-RBXHHCLI.js} +2 -2
  35. package/dist/utils-RBXHHCLI.js.map +1 -0
  36. package/dist/{watch-YGIU7RN7.js → watch-FRLS6FKE.js} +8 -6
  37. package/dist/{watch-YGIU7RN7.js.map → watch-FRLS6FKE.js.map} +1 -1
  38. package/package.json +7 -4
  39. package/scripts/postinstall.cjs +27 -0
  40. package/dist/chunk-34WIGIQC.js.map +0 -1
  41. package/dist/chunk-7FMKAV4P.js +0 -56
  42. package/dist/chunk-7FMKAV4P.js.map +0 -1
  43. package/dist/chunk-7NOZFYH6.js.map +0 -1
  44. package/dist/chunk-T7EBS5XP.js.map +0 -1
  45. package/dist/resolve-673XFZQ6.js.map +0 -1
  46. package/dist/tesseract-provider-MNMZPSGF.js +0 -11
  47. /package/dist/{auto-detect-2YGFYQCN.js.map → auto-detect-CBYICI6B.js.map} +0 -0
  48. /package/dist/{tesseract-provider-MNMZPSGF.js.map → install-commands-P2KTEXQ4.js.map} +0 -0
  49. /package/dist/{utils-DHOODYKU.js.map → pm-7KGLH6MX.js.map} +0 -0
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  detectAvailableOcr,
4
4
  getAutoFallbackChain,
5
- getTesseractFallbackMessage,
5
+ getNoCliMessage,
6
6
  validateOcrMode
7
- } from "./chunk-7NOZFYH6.js";
7
+ } from "./chunk-CLK4PNZ7.js";
8
8
  import "./chunk-ZWE3DS7E.js";
9
9
  export {
10
10
  detectAvailableOcr,
11
11
  getAutoFallbackChain,
12
- getTesseractFallbackMessage,
12
+ getNoCliMessage,
13
13
  validateOcrMode
14
14
  };
15
- //# sourceMappingURL=auto-detect-2YGFYQCN.js.map
15
+ //# sourceMappingURL=auto-detect-CBYICI6B.js.map
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/setup/install-commands.ts
4
+ var POPPLER_RECIPES = {
5
+ brew: { pm: "brew", cmd: "brew install poppler", needsSudo: false },
6
+ winget: { pm: "winget", cmd: "winget install --id oschwartz10612.Poppler", needsSudo: false, notes: "\uC2E4\uC874 \uD655\uC778\uB428 (winget-pkgs 25.07.0-0)" },
7
+ scoop: { pm: "scoop", cmd: "scoop install poppler", needsSudo: false, notes: "main \uBC84\uD0B7 \uD3EC\uD568, PATH \uC790\uB3D9 \uB4F1\uB85D" },
8
+ choco: { pm: "choco", cmd: "choco install poppler -y", needsSudo: true, notes: "\uAD00\uB9AC\uC790 PowerShell \uD544\uC694" },
9
+ apt: { pm: "apt", cmd: "sudo apt-get install -y poppler-utils", needsSudo: true },
10
+ dnf: { pm: "dnf", cmd: "sudo dnf install -y poppler-utils", needsSudo: true },
11
+ yum: { pm: "yum", cmd: "sudo yum install -y poppler-utils", needsSudo: true },
12
+ pacman: { pm: "pacman", cmd: "sudo pacman -S --noconfirm poppler", needsSudo: true },
13
+ zypper: { pm: "zypper", cmd: "sudo zypper install -y poppler-tools", needsSudo: true },
14
+ apk: { pm: "apk", cmd: "apk add --no-cache poppler-utils", needsSudo: false, notes: "Alpine/Docker \uCE5C\uD654" },
15
+ unknown: null
16
+ };
17
+ var LIBREOFFICE_RECIPES = {
18
+ brew: { pm: "brew", cmd: "brew install --cask libreoffice", needsSudo: false },
19
+ winget: { pm: "winget", cmd: "winget install --id TheDocumentFoundation.LibreOffice", needsSudo: false, notes: "\uC2E4\uC874 \uD655\uC778\uB428" },
20
+ scoop: { pm: "scoop", cmd: "scoop bucket add extras && scoop install libreoffice", needsSudo: false },
21
+ choco: { pm: "choco", cmd: "choco install libreoffice-fresh -y", needsSudo: true, notes: "\uAD00\uB9AC\uC790 PowerShell \uD544\uC694" },
22
+ apt: { pm: "apt", cmd: "sudo apt-get install -y libreoffice", needsSudo: true },
23
+ dnf: { pm: "dnf", cmd: "sudo dnf install -y libreoffice", needsSudo: true },
24
+ yum: { pm: "yum", cmd: "sudo yum install -y libreoffice", needsSudo: true },
25
+ pacman: { pm: "pacman", cmd: "sudo pacman -S --noconfirm libreoffice-still", needsSudo: true },
26
+ zypper: { pm: "zypper", cmd: "sudo zypper install -y libreoffice", needsSudo: true },
27
+ apk: { pm: "apk", cmd: "apk add --no-cache libreoffice", needsSudo: false, notes: "Alpine \uBBF8\uC9C0\uC6D0 \uAC00\uB2A5 \u2014 node:20-slim(Debian) \uAD8C\uC7A5" },
28
+ unknown: null
29
+ };
30
+
31
+ export {
32
+ POPPLER_RECIPES,
33
+ LIBREOFFICE_RECIPES
34
+ };
35
+ //# sourceMappingURL=chunk-AEWWERJ5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/setup/install-commands.ts"],"sourcesContent":["import type { PackageManager } from \"./pm.js\"\n\nexport interface InstallRecipe {\n pm: PackageManager\n cmd: string\n needsSudo: boolean\n notes?: string\n}\n\nexport const POPPLER_RECIPES: Record<PackageManager, InstallRecipe | null> = {\n brew: { pm: \"brew\", cmd: \"brew install poppler\", needsSudo: false },\n winget: { pm: \"winget\", cmd: \"winget install --id oschwartz10612.Poppler\", needsSudo: false, notes: \"실존 확인됨 (winget-pkgs 25.07.0-0)\" },\n scoop: { pm: \"scoop\", cmd: \"scoop install poppler\", needsSudo: false, notes: \"main 버킷 포함, PATH 자동 등록\" },\n choco: { pm: \"choco\", cmd: \"choco install poppler -y\", needsSudo: true, notes: \"관리자 PowerShell 필요\" },\n apt: { pm: \"apt\", cmd: \"sudo apt-get install -y poppler-utils\", needsSudo: true },\n dnf: { pm: \"dnf\", cmd: \"sudo dnf install -y poppler-utils\", needsSudo: true },\n yum: { pm: \"yum\", cmd: \"sudo yum install -y poppler-utils\", needsSudo: true },\n pacman: { pm: \"pacman\", cmd: \"sudo pacman -S --noconfirm poppler\", needsSudo: true },\n zypper: { pm: \"zypper\", cmd: \"sudo zypper install -y poppler-tools\", needsSudo: true },\n apk: { pm: \"apk\", cmd: \"apk add --no-cache poppler-utils\", needsSudo: false, notes: \"Alpine/Docker 친화\" },\n unknown: null,\n}\n\nexport const LIBREOFFICE_RECIPES: Record<PackageManager, InstallRecipe | null> = {\n brew: { pm: \"brew\", cmd: \"brew install --cask libreoffice\", needsSudo: false },\n winget: { pm: \"winget\", cmd: \"winget install --id TheDocumentFoundation.LibreOffice\", needsSudo: false, notes: \"실존 확인됨\" },\n scoop: { pm: \"scoop\", cmd: \"scoop bucket add extras && scoop install libreoffice\", needsSudo: false },\n choco: { pm: \"choco\", cmd: \"choco install libreoffice-fresh -y\", needsSudo: true, notes: \"관리자 PowerShell 필요\" },\n apt: { pm: \"apt\", cmd: \"sudo apt-get install -y libreoffice\", needsSudo: true },\n dnf: { pm: \"dnf\", cmd: \"sudo dnf install -y libreoffice\", needsSudo: true },\n yum: { pm: \"yum\", cmd: \"sudo yum install -y libreoffice\", needsSudo: true },\n pacman: { pm: \"pacman\", cmd: \"sudo pacman -S --noconfirm libreoffice-still\", needsSudo: true },\n zypper: { pm: \"zypper\", cmd: \"sudo zypper install -y libreoffice\", needsSudo: true },\n apk: { pm: \"apk\", cmd: \"apk add --no-cache libreoffice\", needsSudo: false, notes: \"Alpine 미지원 가능 — node:20-slim(Debian) 권장\" },\n unknown: null,\n}\n"],"mappings":";;;AASO,IAAM,kBAAgE;AAAA,EAC3E,MAAS,EAAE,IAAI,QAAU,KAAK,wBAAuD,WAAW,MAAM;AAAA,EACtG,QAAS,EAAE,IAAI,UAAU,KAAK,8CAAuD,WAAW,OAAO,OAAO,0DAAiC;AAAA,EAC/I,OAAS,EAAE,IAAI,SAAU,KAAK,yBAAuD,WAAW,OAAO,OAAO,iEAAyB;AAAA,EACvI,OAAS,EAAE,IAAI,SAAU,KAAK,4BAAuD,WAAW,MAAO,OAAO,6CAAoB;AAAA,EAClI,KAAS,EAAE,IAAI,OAAU,KAAK,yCAAuD,WAAW,KAAK;AAAA,EACrG,KAAS,EAAE,IAAI,OAAU,KAAK,qCAAuD,WAAW,KAAK;AAAA,EACrG,KAAS,EAAE,IAAI,OAAU,KAAK,qCAAuD,WAAW,KAAK;AAAA,EACrG,QAAS,EAAE,IAAI,UAAU,KAAK,sCAAuD,WAAW,KAAK;AAAA,EACrG,QAAS,EAAE,IAAI,UAAU,KAAK,wCAAuD,WAAW,KAAK;AAAA,EACrG,KAAS,EAAE,IAAI,OAAU,KAAK,oCAAuD,WAAW,OAAO,OAAO,6BAAmB;AAAA,EACjI,SAAS;AACX;AAEO,IAAM,sBAAoE;AAAA,EAC/E,MAAS,EAAE,IAAI,QAAU,KAAK,mCAAuD,WAAW,MAAM;AAAA,EACtG,QAAS,EAAE,IAAI,UAAU,KAAK,yDAAyD,WAAW,OAAO,OAAO,kCAAS;AAAA,EACzH,OAAS,EAAE,IAAI,SAAU,KAAK,wDAAwD,WAAW,MAAM;AAAA,EACvG,OAAS,EAAE,IAAI,SAAU,KAAK,sCAAuD,WAAW,MAAO,OAAO,6CAAoB;AAAA,EAClI,KAAS,EAAE,IAAI,OAAU,KAAK,uCAAuD,WAAW,KAAK;AAAA,EACrG,KAAS,EAAE,IAAI,OAAU,KAAK,mCAAuD,WAAW,KAAK;AAAA,EACrG,KAAS,EAAE,IAAI,OAAU,KAAK,mCAAuD,WAAW,KAAK;AAAA,EACrG,QAAS,EAAE,IAAI,UAAU,KAAK,gDAAuD,WAAW,KAAK;AAAA,EACrG,QAAS,EAAE,IAAI,UAAU,KAAK,sCAAuD,WAAW,KAAK;AAAA,EACrG,KAAS,EAAE,IAAI,OAAU,KAAK,kCAAuD,WAAW,OAAO,OAAO,kFAA0C;AAAA,EACxJ,SAAS;AACX;","names":[]}
@@ -7,7 +7,7 @@ function detectAvailableOcr() {
7
7
  for (const cli of CLI_PRIORITY) {
8
8
  if (isCliInstalled(cli)) return cli;
9
9
  }
10
- return "tesseract";
10
+ return null;
11
11
  }
12
12
  function isCliInstalled(name) {
13
13
  try {
@@ -23,11 +23,10 @@ function getAutoFallbackChain() {
23
23
  for (const cli of CLI_PRIORITY) {
24
24
  if (isCliInstalled(cli)) chain.push(cli);
25
25
  }
26
- chain.push("tesseract");
27
26
  return chain;
28
27
  }
29
28
  function validateOcrMode(mode) {
30
- if (mode === "auto" || mode === "off" || mode === "tesseract") return;
29
+ if (mode === "auto" || mode === "off") return;
31
30
  if (!isCliInstalled(mode)) {
32
31
  throw new Error(`'${mode}' CLI\uAC00 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
33
32
  ${getInstallGuide(mode)}`);
@@ -42,10 +41,10 @@ function getInstallGuide(mode) {
42
41
  };
43
42
  return guides[mode] || `'${mode}'\uC744(\uB97C) \uC124\uCE58\uD574\uC8FC\uC138\uC694.`;
44
43
  }
45
- function getTesseractFallbackMessage() {
44
+ function getNoCliMessage() {
46
45
  return [
47
- "\uC124\uCE58\uB41C AI CLI\uAC00 \uC5C6\uC5B4 \uB0B4\uC7A5 tesseract.js\uB85C OCR\uC744 \uC218\uD589\uD569\uB2C8\uB2E4.",
48
- "\uB354 \uB098\uC740 \uD488\uC9C8(\uD14C\uC774\uBE14/\uD5E4\uB529 \uAD6C\uC870 \uBCF4\uC874)\uC744 \uC704\uD574 AI CLI \uC124\uCE58\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4:",
46
+ "\uC124\uCE58\uB41C AI CLI\uAC00 \uC5C6\uC5B4 OCR\uC744 \uC218\uD589\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
47
+ "\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF \uCC98\uB9AC\uB97C \uC704\uD574 AI CLI \uC124\uCE58\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4:",
49
48
  "",
50
49
  " [\uAD8C\uC7A5] Codex CLI: npm install -g @openai/codex",
51
50
  " Gemini CLI: https://ai.google.dev/gemini-api/docs/cli",
@@ -58,6 +57,6 @@ export {
58
57
  detectAvailableOcr,
59
58
  getAutoFallbackChain,
60
59
  validateOcrMode,
61
- getTesseractFallbackMessage
60
+ getNoCliMessage
62
61
  };
63
- //# sourceMappingURL=chunk-7NOZFYH6.js.map
62
+ //# sourceMappingURL=chunk-CLK4PNZ7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ocr/auto-detect.ts"],"sourcesContent":["/**\n * OCR CLI 자동 탐색\n *\n * 탐색 순서: codex → gemini → claude → ollama\n * CLI는 which(unix) / where(win) 명령어로 PATH 존재 확인.\n */\n\nimport { execSync } from \"child_process\"\nimport type { OcrMode } from \"../types.js\"\n\n/** CLI 탐색 우선순위 */\nconst CLI_PRIORITY = [\"codex\", \"gemini\", \"claude\", \"ollama\"] as const\n\n/**\n * 시스템에 설치된 OCR 도구를 우선순위대로 탐색.\n * @returns 사용 가능한 OcrMode, 없으면 null\n */\nexport function detectAvailableOcr(): OcrMode | null {\n for (const cli of CLI_PRIORITY) {\n if (isCliInstalled(cli)) return cli\n }\n return null\n}\n\n/**\n * 특정 CLI가 시스템 PATH에 있는지 확인.\n * which(unix) 또는 where(win32) 사용.\n */\nfunction isCliInstalled(name: string): boolean {\n try {\n const cmd = process.platform === \"win32\" ? \"where\" : \"which\"\n execSync(`${cmd} ${name}`, { stdio: \"ignore\", timeout: 3000 })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * auto 모드에서 시도할 fallback 체인 반환.\n * 설치된 CLI만 포함.\n */\nexport function getAutoFallbackChain(): OcrMode[] {\n const chain: OcrMode[] = []\n for (const cli of CLI_PRIORITY) {\n if (isCliInstalled(cli)) chain.push(cli)\n }\n return chain\n}\n\n/**\n * 수동 지정된 OcrMode 유효성 검증.\n * --ocr gemini 등 강제 지정 시 호출.\n * @throws 해당 CLI가 설치되지 않은 경우 Error\n */\nexport function validateOcrMode(mode: OcrMode): void {\n if (mode === \"auto\" || mode === \"off\") return\n\n if (!isCliInstalled(mode)) {\n throw new Error(`'${mode}' CLI가 설치되지 않았습니다.\\n${getInstallGuide(mode)}`)\n }\n}\n\n/** CLI별 설치 안내 메시지 */\nfunction getInstallGuide(mode: string): string {\n const guides: Record<string, string> = {\n gemini: \"설치: https://ai.google.dev/gemini-api/docs/cli\",\n claude: \"설치: npm install -g @anthropic-ai/claude-code 또는 https://claude.ai/code\",\n codex: \"설치: npm install -g @openai/codex 또는 https://github.com/openai/codex\",\n ollama: \"설치: brew install ollama 또는 https://ollama.com/download\",\n }\n return guides[mode] || `'${mode}'을(를) 설치해주세요.`\n}\n\n/**\n * AI CLI가 없어 OCR 불가 시 표시할 안내 메시지.\n */\nexport function getNoCliMessage(): string {\n return [\n \"설치된 AI CLI가 없어 OCR을 수행할 수 없습니다.\",\n \"이미지 기반 PDF 처리를 위해 AI CLI 설치를 권장합니다:\",\n \"\",\n \" [권장] Codex CLI: npm install -g @openai/codex\",\n \" Gemini CLI: https://ai.google.dev/gemini-api/docs/cli\",\n \" Claude CLI: npm install -g @anthropic-ai/claude-code\",\n \" Ollama: brew install ollama (+ ollama pull gemma4:27b)\",\n ].join(\"\\n\")\n}\n"],"mappings":";;;AAOA,SAAS,gBAAgB;AAIzB,IAAM,eAAe,CAAC,SAAS,UAAU,UAAU,QAAQ;AAMpD,SAAS,qBAAqC;AACnD,aAAW,OAAO,cAAc;AAC9B,QAAI,eAAe,GAAG,EAAG,QAAO;AAAA,EAClC;AACA,SAAO;AACT;AAMA,SAAS,eAAe,MAAuB;AAC7C,MAAI;AACF,UAAM,MAAM,QAAQ,aAAa,UAAU,UAAU;AACrD,aAAS,GAAG,GAAG,IAAI,IAAI,IAAI,EAAE,OAAO,UAAU,SAAS,IAAK,CAAC;AAC7D,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,uBAAkC;AAChD,QAAM,QAAmB,CAAC;AAC1B,aAAW,OAAO,cAAc;AAC9B,QAAI,eAAe,GAAG,EAAG,OAAM,KAAK,GAAG;AAAA,EACzC;AACA,SAAO;AACT;AAOO,SAAS,gBAAgB,MAAqB;AACnD,MAAI,SAAS,UAAU,SAAS,MAAO;AAEvC,MAAI,CAAC,eAAe,IAAI,GAAG;AACzB,UAAM,IAAI,MAAM,IAAI,IAAI;AAAA,EAAuB,gBAAgB,IAAI,CAAC,EAAE;AAAA,EACxE;AACF;AAGA,SAAS,gBAAgB,MAAsB;AAC7C,QAAM,SAAiC;AAAA,IACrC,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,OAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AACA,SAAO,OAAO,IAAI,KAAK,IAAI,IAAI;AACjC;AAKO,SAAS,kBAA0B;AACxC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;","names":[]}
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __require
4
+ } from "./chunk-ZWE3DS7E.js";
5
+
6
+ // src/setup/detect.ts
7
+ import { spawnSync } from "child_process";
8
+ import { existsSync } from "fs";
9
+ import { join } from "path";
10
+ var isWin = process.platform === "win32";
11
+ function which(cmd) {
12
+ const finder = isWin ? "where" : "which";
13
+ const r = spawnSync(finder, [cmd], { encoding: "utf8", shell: isWin });
14
+ if (r.status !== 0) return void 0;
15
+ return r.stdout.split(/\r?\n/).find(Boolean)?.trim();
16
+ }
17
+ function resolveCmd(cmd, envKey) {
18
+ const override = process.env[envKey];
19
+ if (override) return override;
20
+ const found = which(cmd);
21
+ if (found) return found;
22
+ if (isWin) return findWinCandidate(cmd);
23
+ return void 0;
24
+ }
25
+ function findWinCandidate(cmd) {
26
+ const exe = cmd.endsWith(".exe") ? cmd : `${cmd}.exe`;
27
+ const userProfile = process.env["USERPROFILE"] ?? "C:\\Users\\Default";
28
+ const localAppData = process.env["LOCALAPPDATA"] ?? join(userProfile, "AppData", "Local");
29
+ const programFiles = process.env["ProgramFiles"] ?? "C:\\Program Files";
30
+ const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
31
+ const isPopplerExe = ["pdftoppm.exe", "pdfinfo.exe", "pdftotext.exe"].includes(exe);
32
+ const isSoffice = exe === "soffice.exe";
33
+ if (isPopplerExe) return findPopplerWin(exe, { userProfile, localAppData, programFiles, programFilesX86 });
34
+ if (isSoffice) return findSofficeWin({ programFiles, programFilesX86, localAppData });
35
+ return void 0;
36
+ }
37
+ function findPopplerWin(exe, p) {
38
+ const scoopPath = join(p.userProfile, "scoop", "apps", "poppler", "current", "bin", exe);
39
+ if (existsSync(scoopPath)) return scoopPath;
40
+ const wingetBase = join(p.localAppData, "Microsoft", "WinGet", "Packages");
41
+ const wingetResult = globFirst(wingetBase, ["oschwartz10612.Poppler_*"], ["poppler-*", "Library", "bin", exe]);
42
+ if (wingetResult) return wingetResult;
43
+ const chocoBase = join("C:", "ProgramData", "chocolatey", "lib", "poppler", "tools");
44
+ const chocoResult = globFirst(chocoBase, ["poppler-*"], ["bin", exe]);
45
+ if (chocoResult) return chocoResult;
46
+ for (const base of [
47
+ join("C:", "poppler", "bin", exe),
48
+ join("C:", "tools", "poppler", "bin", exe),
49
+ join(p.programFiles, "poppler", "bin", exe),
50
+ join(p.programFilesX86, "poppler", "bin", exe),
51
+ join(p.localAppData, "Programs", "poppler", "bin", exe)
52
+ ]) {
53
+ if (existsSync(base)) return base;
54
+ }
55
+ return void 0;
56
+ }
57
+ function findSofficeWin(p) {
58
+ for (const candidate of [
59
+ join(p.programFiles, "LibreOffice", "program", "soffice.exe"),
60
+ join(p.programFilesX86, "LibreOffice", "program", "soffice.exe"),
61
+ join(p.localAppData, "Programs", "LibreOffice", "program", "soffice.exe"),
62
+ // winget 설치는 Program Files와 동일
63
+ join(p.programFiles, "LibreOffice 24", "program", "soffice.exe"),
64
+ join(p.programFiles, "LibreOffice 25", "program", "soffice.exe")
65
+ ]) {
66
+ if (existsSync(candidate)) return candidate;
67
+ }
68
+ return void 0;
69
+ }
70
+ function globFirst(base, wildcardSegments, rest) {
71
+ if (!existsSync(base)) return void 0;
72
+ let current = base;
73
+ for (const seg of wildcardSegments) {
74
+ if (!seg.includes("*")) {
75
+ current = join(current, seg);
76
+ if (!existsSync(current)) return void 0;
77
+ continue;
78
+ }
79
+ try {
80
+ const { readdirSync } = __require("fs");
81
+ const entries = readdirSync(current);
82
+ const pattern = seg.replace(/\*/g, ".*");
83
+ const re = new RegExp(`^${pattern}$`, "i");
84
+ const match = entries.find((e) => re.test(e));
85
+ if (!match) return void 0;
86
+ current = join(current, match);
87
+ } catch {
88
+ return void 0;
89
+ }
90
+ }
91
+ const final = join(current, ...rest);
92
+ return existsSync(final) ? final : void 0;
93
+ }
94
+ function probe(cmd, envKey, versionArgs = ["--version"]) {
95
+ const resolvedPath = resolveCmd(cmd, envKey);
96
+ if (!resolvedPath) return { name: cmd, found: false };
97
+ const v = spawnSync(resolvedPath, versionArgs, { encoding: "utf8", shell: isWin });
98
+ const versionLine = (v.stdout || v.stderr).split(/\r?\n/)[0]?.trim();
99
+ return { name: cmd, found: true, path: resolvedPath, version: versionLine };
100
+ }
101
+ function probeOcrToolchain() {
102
+ return {
103
+ // poppler는 --version이 비표준이므로 -v 사용
104
+ pdftoppm: probe("pdftoppm", "KORDOC_PDFTOPPM_PATH", ["-v"]),
105
+ pdfinfo: probe("pdfinfo", "KORDOC_PDFINFO_PATH", ["-v"]),
106
+ pdftotext: probe("pdftotext", "KORDOC_PDFTOTEXT_PATH", ["-v"]),
107
+ soffice: probe("soffice", "KORDOC_SOFFICE_PATH", ["--version"])
108
+ };
109
+ }
110
+ function isWSL() {
111
+ if (process.platform !== "linux") return false;
112
+ try {
113
+ const { readFileSync } = __require("fs");
114
+ const version = readFileSync("/proc/version", "utf8");
115
+ return /microsoft|wsl/i.test(version);
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+
121
+ export {
122
+ probeOcrToolchain,
123
+ isWSL
124
+ };
125
+ //# sourceMappingURL=chunk-CPTOBJJD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/setup/detect.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\"\nimport { existsSync } from \"node:fs\"\nimport { join } from \"node:path\"\n\nexport interface ToolStatus {\n name: string\n found: boolean\n path?: string\n version?: string\n}\n\nconst isWin = process.platform === \"win32\"\n\nfunction which(cmd: string): string | undefined {\n const finder = isWin ? \"where\" : \"which\"\n const r = spawnSync(finder, [cmd], { encoding: \"utf8\", shell: isWin })\n if (r.status !== 0) return undefined\n return r.stdout.split(/\\r?\\n/).find(Boolean)?.trim()\n}\n\n/** 환경변수 override → PATH 탐색 → Windows 후보 경로 순으로 감지 */\nfunction resolveCmd(cmd: string, envKey: string): string | undefined {\n const override = process.env[envKey]\n if (override) return override\n\n const found = which(cmd)\n if (found) return found\n\n if (isWin) return findWinCandidate(cmd)\n return undefined\n}\n\n/**\n * Windows에서 PATH 미등록 설치 경로를 와일드카드 방식으로 탐색.\n * scoop / winget / choco / 수동 설치 경로를 순서대로 시도.\n */\nfunction findWinCandidate(cmd: string): string | undefined {\n const exe = cmd.endsWith(\".exe\") ? cmd : `${cmd}.exe`\n const userProfile = process.env[\"USERPROFILE\"] ?? \"C:\\\\Users\\\\Default\"\n const localAppData = process.env[\"LOCALAPPDATA\"] ?? join(userProfile, \"AppData\", \"Local\")\n const programFiles = process.env[\"ProgramFiles\"] ?? \"C:\\\\Program Files\"\n const programFilesX86 = process.env[\"ProgramFiles(x86)\"] ?? \"C:\\\\Program Files (x86)\"\n\n const isPopplerExe = [\"pdftoppm.exe\", \"pdfinfo.exe\", \"pdftotext.exe\"].includes(exe)\n const isSoffice = exe === \"soffice.exe\"\n\n if (isPopplerExe) return findPopplerWin(exe, { userProfile, localAppData, programFiles, programFilesX86 })\n if (isSoffice) return findSofficeWin({ programFiles, programFilesX86, localAppData })\n return undefined\n}\n\ninterface WinPaths {\n userProfile: string\n localAppData: string\n programFiles: string\n programFilesX86: string\n}\n\nfunction findPopplerWin(exe: string, p: WinPaths): string | undefined {\n // scoop: %USERPROFILE%\\scoop\\apps\\poppler\\current\\bin\\\n const scoopPath = join(p.userProfile, \"scoop\", \"apps\", \"poppler\", \"current\", \"bin\", exe)\n if (existsSync(scoopPath)) return scoopPath\n\n // winget: %LOCALAPPDATA%\\Microsoft\\WinGet\\Packages\\oschwartz10612.Poppler_*\\poppler-*\\Library\\bin\\\n const wingetBase = join(p.localAppData, \"Microsoft\", \"WinGet\", \"Packages\")\n const wingetResult = globFirst(wingetBase, [\"oschwartz10612.Poppler_*\"], [\"poppler-*\", \"Library\", \"bin\", exe])\n if (wingetResult) return wingetResult\n\n // choco: C:\\ProgramData\\chocolatey\\lib\\poppler\\tools\\poppler-*\\bin\\\n const chocoBase = join(\"C:\", \"ProgramData\", \"chocolatey\", \"lib\", \"poppler\", \"tools\")\n const chocoResult = globFirst(chocoBase, [\"poppler-*\"], [\"bin\", exe])\n if (chocoResult) return chocoResult\n\n // 수동 설치 일반 경로\n for (const base of [\n join(\"C:\", \"poppler\", \"bin\", exe),\n join(\"C:\", \"tools\", \"poppler\", \"bin\", exe),\n join(p.programFiles, \"poppler\", \"bin\", exe),\n join(p.programFilesX86, \"poppler\", \"bin\", exe),\n join(p.localAppData, \"Programs\", \"poppler\", \"bin\", exe),\n ]) {\n if (existsSync(base)) return base\n }\n\n return undefined\n}\n\nfunction findSofficeWin(p: Pick<WinPaths, \"programFiles\" | \"programFilesX86\" | \"localAppData\">): string | undefined {\n for (const candidate of [\n join(p.programFiles, \"LibreOffice\", \"program\", \"soffice.exe\"),\n join(p.programFilesX86, \"LibreOffice\", \"program\", \"soffice.exe\"),\n join(p.localAppData, \"Programs\", \"LibreOffice\", \"program\", \"soffice.exe\"),\n // winget 설치는 Program Files와 동일\n join(p.programFiles, \"LibreOffice 24\", \"program\", \"soffice.exe\"),\n join(p.programFiles, \"LibreOffice 25\", \"program\", \"soffice.exe\"),\n ]) {\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\n/**\n * 와일드카드 패턴(*)이 포함된 단계적 경로를 첫 번째 매칭으로 resolve.\n * readdirSync로 해당 디렉토리 엔트리 중 패턴 매칭하는 첫 번째를 사용.\n */\nfunction globFirst(base: string, wildcardSegments: string[], rest: string[]): string | undefined {\n if (!existsSync(base)) return undefined\n\n let current = base\n for (const seg of wildcardSegments) {\n if (!seg.includes(\"*\")) {\n current = join(current, seg)\n if (!existsSync(current)) return undefined\n continue\n }\n try {\n const { readdirSync } = require(\"node:fs\") as typeof import(\"node:fs\")\n const entries = readdirSync(current)\n const pattern = seg.replace(/\\*/g, \".*\")\n const re = new RegExp(`^${pattern}$`, \"i\")\n const match = entries.find(e => re.test(e))\n if (!match) return undefined\n current = join(current, match)\n } catch {\n return undefined\n }\n }\n\n const final = join(current, ...rest)\n return existsSync(final) ? final : undefined\n}\n\nexport function probe(cmd: string, envKey: string, versionArgs: string[] = [\"--version\"]): ToolStatus {\n const resolvedPath = resolveCmd(cmd, envKey)\n if (!resolvedPath) return { name: cmd, found: false }\n\n const v = spawnSync(resolvedPath, versionArgs, { encoding: \"utf8\", shell: isWin })\n const versionLine = (v.stdout || v.stderr).split(/\\r?\\n/)[0]?.trim()\n return { name: cmd, found: true, path: resolvedPath, version: versionLine }\n}\n\nexport interface OcrToolchainStatus {\n pdftoppm: ToolStatus\n pdfinfo: ToolStatus\n pdftotext: ToolStatus\n soffice: ToolStatus\n}\n\nexport function probeOcrToolchain(): OcrToolchainStatus {\n return {\n // poppler는 --version이 비표준이므로 -v 사용\n pdftoppm: probe(\"pdftoppm\", \"KORDOC_PDFTOPPM_PATH\", [\"-v\"]),\n pdfinfo: probe(\"pdfinfo\", \"KORDOC_PDFINFO_PATH\", [\"-v\"]),\n pdftotext: probe(\"pdftotext\", \"KORDOC_PDFTOTEXT_PATH\", [\"-v\"]),\n soffice: probe(\"soffice\", \"KORDOC_SOFFICE_PATH\", [\"--version\"]),\n }\n}\n\n/** WSL 환경 여부 감지 (Node.js가 WSL 내부 Linux로 실행 중인지) */\nexport function isWSL(): boolean {\n if (process.platform !== \"linux\") return false\n try {\n const { readFileSync } = require(\"node:fs\") as typeof import(\"node:fs\")\n const version = readFileSync(\"/proc/version\", \"utf8\")\n return /microsoft|wsl/i.test(version)\n } catch {\n return false\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/utils.ts
4
- var VERSION = true ? "2.4.17" : "0.0.0-dev";
4
+ var VERSION = true ? "2.5.0" : "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-W2KDIKDF.js.map
108
+ //# sourceMappingURL=chunk-IJGNPAK2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["/** kordoc 공용 유틸리티 */\nimport type { ErrorCode } from \"./types.js\"\nimport type { LogStage } from \"./logging/logger.js\"\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n code?: ErrorCode | \"UNKNOWN\"\n stage?: LogStage\n constructor(message: string, opts: { code?: ErrorCode | \"UNKNOWN\"; stage?: LogStage } = {}) {\n super(message)\n this.name = \"KordocError\"\n this.code = opts.code\n this.stage = opts.stage\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n return normalized.includes(\"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n buffer: ArrayBuffer,\n maxUncompressedSize = 100 * 1024 * 1024,\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n return trimmed\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n\n/** unknown 에러를 KordocError(code/stage 포함)로 정규화 */\nexport function normalizeKordocError(\n err: unknown,\n fallbackMessage: string,\n stage: LogStage = \"unknown\",\n fallbackCode: ErrorCode | \"UNKNOWN\" = \"PARSE_ERROR\",\n): KordocError {\n if (err instanceof KordocError) {\n if (!err.stage) err.stage = stage\n if (!err.code) err.code = fallbackCode\n return err\n }\n const message = err instanceof Error ? err.message : fallbackMessage\n const code = err instanceof Error ? classifyError(err) : fallbackCode\n return new KordocError(message || fallbackMessage, { code, stage })\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["/** kordoc 공용 유틸리티 */\nimport type { ErrorCode } from \"./types.js\"\nimport type { LogStage } from \"./logging/logger.js\"\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n code?: ErrorCode | \"UNKNOWN\"\n stage?: LogStage\n constructor(message: string, opts: { code?: ErrorCode | \"UNKNOWN\"; stage?: LogStage } = {}) {\n super(message)\n this.name = \"KordocError\"\n this.code = opts.code\n this.stage = opts.stage\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n return normalized.includes(\"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n buffer: ArrayBuffer,\n maxUncompressedSize = 100 * 1024 * 1024,\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n return trimmed\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n\n/** unknown 에러를 KordocError(code/stage 포함)로 정규화 */\nexport function normalizeKordocError(\n err: unknown,\n fallbackMessage: string,\n stage: LogStage = \"unknown\",\n fallbackCode: ErrorCode | \"UNKNOWN\" = \"PARSE_ERROR\",\n): KordocError {\n if (err instanceof KordocError) {\n if (!err.stage) err.stage = stage\n if (!err.code) err.code = fallbackCode\n return err\n }\n const message = err instanceof Error ? err.message : fallbackMessage\n const code = err instanceof Error ? classifyError(err) : fallbackCode\n return new KordocError(message || fallbackMessage, { code, stage })\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- markdownToBlocks
4
- } from "./chunk-YW5G6BCJ.js";
5
2
  import {
6
3
  KordocError,
7
4
  classifyError,
@@ -10,16 +7,16 @@ import {
10
7
  precheckZipSize,
11
8
  sanitizeHref,
12
9
  toArrayBuffer
13
- } from "./chunk-W2KDIKDF.js";
10
+ } from "./chunk-IJGNPAK2.js";
14
11
  import {
15
12
  parsePageRange
16
13
  } from "./chunk-MOL7MDBG.js";
17
- import {
18
- createTesseractProvider
19
- } from "./chunk-7FMKAV4P.js";
20
14
  import {
21
15
  createCliOcrProvider
22
- } from "./chunk-34WIGIQC.js";
16
+ } from "./chunk-Y4WFKJ5P.js";
17
+ import {
18
+ markdownToBlocks
19
+ } from "./chunk-YW5G6BCJ.js";
23
20
  import {
24
21
  createLoggerFromEnv,
25
22
  generateRunId
@@ -30,9 +27,9 @@ import {
30
27
  __toESM
31
28
  } from "./chunk-ZWE3DS7E.js";
32
29
 
33
- // node_modules/cfb/cfb.js
30
+ // ../../../node_modules/cfb/cfb.js
34
31
  var require_cfb = __commonJS({
35
- "node_modules/cfb/cfb.js"(exports, module) {
32
+ "../../../node_modules/cfb/cfb.js"(exports, module) {
36
33
  "use strict";
37
34
  var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
38
35
  function Base64_encode(input) {
@@ -5688,7 +5685,7 @@ async function parsePdfDocument(buffer, options) {
5688
5685
  if (ocrMode === "off") {
5689
5686
  throw Object.assign(new KordocError(`\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF (${pageCount}\uD398\uC774\uC9C0, ${totalChars}\uC790)`), { isImageBased: true });
5690
5687
  }
5691
- const { resolveOcrProvider } = await import("./resolve-673XFZQ6.js");
5688
+ const { resolveOcrProvider } = await import("./resolve-XWYJYKKH.js");
5692
5689
  const { ocrPages } = await import("./provider-T2D5XRTI.js");
5693
5690
  const tryProvider = async (provider, filter) => {
5694
5691
  try {
@@ -5709,7 +5706,7 @@ async function parsePdfDocument(buffer, options) {
5709
5706
  if (options?.ocr) {
5710
5707
  ocrBlocks = await tryProvider(options.ocr, pageFilter);
5711
5708
  } else if (ocrMode === "auto") {
5712
- const { getAutoFallbackChain } = await import("./auto-detect-2YGFYQCN.js");
5709
+ const { getAutoFallbackChain } = await import("./auto-detect-CBYICI6B.js");
5713
5710
  const pendingPages = /* @__PURE__ */ new Set();
5714
5711
  for (let i = 1; i <= effectivePageCount; i++) {
5715
5712
  if (!pageFilter || pageFilter.has(i)) pendingPages.add(i);
@@ -9826,8 +9823,6 @@ async function markdownToXlsx(markdown, options) {
9826
9823
 
9827
9824
  // src/pipeline/unified-ocr.ts
9828
9825
  import { performance } from "perf_hooks";
9829
- import libre from "libreoffice-convert";
9830
- var libreConvert = libre.convert;
9831
9826
  var OCR_PROMPT = [
9832
9827
  "\uC774 PDF \uD398\uC774\uC9C0 \uC774\uBBF8\uC9C0\uC5D0\uC11C \uD14D\uC2A4\uD2B8\uC640 \uD45C\uB97C \uCD94\uCD9C\uD558\uC5EC Markdown\uC73C\uB85C \uBCC0\uD658\uD558\uACE0, OCR \uC624\uC778\uC2DD \uC624\uB958\uB97C \uC989\uC2DC \uAD50\uC815\uD558\uC5EC \uCD5C\uC885 \uACB0\uACFC\uBB3C\uC744 \uCD9C\uB825\uD558\uC138\uC694.",
9833
9828
  "",
@@ -9931,9 +9926,6 @@ async function parseImage(buffer, options) {
9931
9926
  if (ocrMode === "gemini" || ocrMode === "claude" || ocrMode === "codex" || ocrMode === "ollama") {
9932
9927
  ocrProvider = createCliOcrProvider(ocrMode);
9933
9928
  actualOcrMode = ocrMode;
9934
- } else if (ocrMode === "tesseract") {
9935
- ocrProvider = await createTesseractProvider();
9936
- actualOcrMode = ocrMode;
9937
9929
  } else if (ocrMode === "auto") {
9938
9930
  const modesToTry = ["gemini", "claude", "codex", "ollama"];
9939
9931
  for (const mode of modesToTry) {
@@ -9945,10 +9937,6 @@ async function parseImage(buffer, options) {
9945
9937
  console.warn(`[kordoc] OCR auto-detection: ${mode} CLI not available or failed. Trying next.`, e);
9946
9938
  }
9947
9939
  }
9948
- if (!ocrProvider) {
9949
- ocrProvider = await createTesseractProvider();
9950
- actualOcrMode = "tesseract";
9951
- }
9952
9940
  }
9953
9941
  if (!ocrProvider) {
9954
9942
  return { success: false, fileType: "image", error: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C OCR \uD504\uB85C\uBC14\uC774\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", code: "PARSE_ERROR" };
@@ -10216,4 +10204,4 @@ export {
10216
10204
  cfb/cfb.js:
10217
10205
  (*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com *)
10218
10206
  */
10219
- //# sourceMappingURL=chunk-T7EBS5XP.js.map
10207
+ //# sourceMappingURL=chunk-NKUNXGWI.js.map