hwpx-bridge 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -10
- package/bin/hwpx-bridge.mjs +2 -2
- package/lib/filee.mjs +4 -1
- package/lib/setup.mjs +100 -46
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,29 +9,40 @@ AI 에이전트가 한글 문서(HWP·HWPX)를 다룰 수 있게 해 주는 **MC
|
|
|
9
9
|
|
|
10
10
|
## 설치 — 한 줄
|
|
11
11
|
|
|
12
|
-
[Node.js](https://nodejs.org) 20 이상이 있으면(kordoc과 같은 조건) 아무 터미널에서:
|
|
13
|
-
|
|
14
12
|
```
|
|
15
13
|
npx -y hwpx-bridge setup
|
|
16
14
|
```
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
터미널(PowerShell 등)에 입력하든, Codex·Claude Code에 "이 명령 실행해줘"라고 하든 결과는 같습니다. **이 PC에 설치된 AI 앱을 모두 찾아 한 번에 등록**합니다. 묻는 것은 없습니다.
|
|
17
|
+
|
|
18
|
+
| 찾는 앱 | 등록 위치 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Claude 앱 | `claude_desktop_config.json` |
|
|
21
|
+
| Claude Code | `claude mcp add --scope user` |
|
|
22
|
+
| Codex (ChatGPT 데스크톱 앱·CLI·IDE 확장이 함께 쓰는 설정) | `~/.codex/config.toml` |
|
|
23
|
+
| Cursor, Gemini CLI, Windsurf | 각 앱의 MCP 설정 파일 |
|
|
24
|
+
|
|
25
|
+
등록한 앱을 한 번 완전히 종료했다가 다시 열면, 그 앱의 **모든 대화**에서 도구가 보입니다. 작업 폴더와 상관없이 PC 안의 어느 파일이든 경로로 다룰 수 있습니다. 나중에 새 AI 앱을 설치했다면 같은 명령을 한 번 더 실행하면 됩니다. 기존 설정은 백업(`.bak-…`)을 남기고 유지됩니다.
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
**Node.js가 없는 Windows PC**: `npx`가 없다는 오류가 나면 PowerShell에 아래 한 줄을 넣으세요. Node.js LTS를 winget으로 설치한 뒤 위 명령을 대신 실행합니다.
|
|
21
28
|
|
|
22
29
|
```
|
|
23
|
-
|
|
30
|
+
irm https://raw.githubusercontent.com/ThisIsMarkive/foryourdocument/main/install.ps1 | iex
|
|
24
31
|
```
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
일부 앱에만 등록하려면 `npx -y hwpx-bridge setup --client claude-desktop,codex` (값: `claude-desktop, claude-code, codex, cursor, gemini, windsurf`).
|
|
34
|
+
|
|
35
|
+
웹·모바일(claude.ai 웹, ChatGPT 웹, 휴대폰 앱)은 내 PC의 프로그램을 실행할 수 없어 지원되지 않습니다.
|
|
36
|
+
|
|
37
|
+
설정 파일에 직접 넣는 경우 (`npx -y hwpx-bridge config`가 출력하는 것과 같음):
|
|
27
38
|
|
|
28
39
|
```json
|
|
29
|
-
{ "mcpServers": { "hwpx-bridge": { "command": "npx", "args": ["-y", "hwpx-bridge@0.3.
|
|
40
|
+
{ "mcpServers": { "hwpx-bridge": { "command": "npx", "args": ["-y", "hwpx-bridge@0.3.3", "mcp"] } } }
|
|
30
41
|
```
|
|
31
42
|
|
|
32
|
-
Windows의 Claude 앱은 `"command": "cmd", "args": ["/c", "npx", "-y", "hwpx-bridge@0.3.
|
|
43
|
+
Windows의 Claude 앱은 `"command": "cmd", "args": ["/c", "npx", "-y", "hwpx-bridge@0.3.3", "mcp"]` 형식이 필요하며, `setup`이 자동으로 맞춥니다. 처음 실행할 때 엔진(약 100 MB)을 내려받으므로, `setup`을 먼저 실행해 두면 앱 첫 실행이 빠릅니다.
|
|
33
44
|
|
|
34
|
-
Claude Code·Codex 플러그인 마켓플레이스(`ThisIsMarkive/foryourdocument`)로 설치해도 같은 서버(`npx -y hwpx-bridge@0.3.
|
|
45
|
+
Claude Code·Codex 플러그인 마켓플레이스(`ThisIsMarkive/foryourdocument`)로 설치해도 같은 서버(`npx -y hwpx-bridge@0.3.3 mcp`)와 사용 안내 스킬이 등록됩니다.
|
|
35
46
|
|
|
36
47
|
확인: `npx -y hwpx-bridge doctor`
|
|
37
48
|
|
|
@@ -79,7 +90,7 @@ Filee는 소스를 빌드하지 않고 **공식 릴리스의 파일을 그대로
|
|
|
79
90
|
## 배포 (관리자용)
|
|
80
91
|
|
|
81
92
|
1. npm 계정을 만들고(2단계 인증 필수) Granular Access Token을 만듭니다: 권한 Read and write, 대상 All packages, **Bypass 2FA** 체크, 만료 최대 90일. GitHub 저장소 Secret `NPM_TOKEN`으로 등록합니다. 첫 배포 뒤에는 각 패키지 설정에서 Trusted Publishing(GitHub Actions)을 켜면 토큰 갱신이 필요 없습니다.
|
|
82
|
-
2. 세 `package.json`과 플러그인 매니페스트의 버전을
|
|
93
|
+
2. 세 `package.json`과 플러그인 매니페스트의 버전을 올리고 GitHub Releases에서 `vX.Y.Z` 태그로 릴리스를 만듭니다(태그만 푸시하면 배포되지 않습니다).
|
|
83
94
|
3. GitHub Actions가 Filee 준비 → 패키지 생성 → Windows·Linux에서 실제 설치·변환 테스트 → npm 배포를 순서대로 합니다.
|
|
84
95
|
|
|
85
96
|
로컬에서 확인하려면: `node npm/scripts/prepare-filee.mjs all` 후 각 폴더에서 `npm pack`.
|
package/bin/hwpx-bridge.mjs
CHANGED
|
@@ -7,8 +7,8 @@ const [command = 'help', ...rest] = process.argv.slice(2);
|
|
|
7
7
|
|
|
8
8
|
const HELP = `hwpx-bridge ${pkg.version} — kordoc + Filee document conversion as one MCP server
|
|
9
9
|
|
|
10
|
-
npx -y hwpx-bridge setup AI 앱(Claude
|
|
11
|
-
npx -y hwpx-bridge setup --client claude-desktop,codex
|
|
10
|
+
npx -y hwpx-bridge setup 이 PC의 AI 앱(Claude 앱, Claude Code, Codex, Cursor ...) 모두에 등록
|
|
11
|
+
npx -y hwpx-bridge setup --client claude-desktop,codex 일부 앱에만 등록
|
|
12
12
|
npx -y hwpx-bridge config 직접 붙여 넣을 MCP 설정 JSON 출력
|
|
13
13
|
npx -y hwpx-bridge doctor kordoc·Filee 실행 확인
|
|
14
14
|
npx -y hwpx-bridge mcp MCP 서버 실행 (앱이 이 명령을 실행합니다)
|
package/lib/filee.mjs
CHANGED
|
@@ -21,7 +21,10 @@ export function locateFilee() {
|
|
|
21
21
|
let root;
|
|
22
22
|
try { root = path.dirname(require.resolve(`${PLATFORM_PACKAGE}/package.json`)); }
|
|
23
23
|
catch {
|
|
24
|
-
|
|
24
|
+
const supported = ['win32-x64', 'linux-x64'].includes(`${process.platform}-${process.arch}`);
|
|
25
|
+
return { error: supported
|
|
26
|
+
? `${PLATFORM_PACKAGE} is not installed (npm skipped it). Clear the npx cache (%LOCALAPPDATA%\\npm-cache\\_npx on Windows, ~/.npm/_npx elsewhere) and run setup again. kordoc tools still work.`
|
|
27
|
+
: `Filee conversion is not available on ${process.platform}-${process.arch} (Windows x64 and Linux x64 only). kordoc tools still work.` };
|
|
25
28
|
}
|
|
26
29
|
const cli = path.join(root, 'filee', process.platform === 'win32' ? 'filee-cli.exe' : 'filee-cli');
|
|
27
30
|
if (!fss.existsSync(cli)) return { error: `${PLATFORM_PACKAGE} is installed but ${cli} is missing.` };
|
package/lib/setup.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
// `npx -y hwpx-bridge setup` — register the MCP server in AI
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// `npx -y hwpx-bridge setup` — register the MCP server in every AI app found on this computer.
|
|
2
|
+
//
|
|
3
|
+
// No questions: wherever the command runs (a terminal, Codex, Claude Code ...), every installed app gets
|
|
4
|
+
// the same entry. `--client a,b` limits it to some apps. Config files are merged in place with a backup;
|
|
5
|
+
// Claude Code is registered through its own CLI. Nothing else on the computer is changed.
|
|
4
6
|
import fs from 'node:fs/promises';
|
|
5
7
|
import fss from 'node:fs';
|
|
6
8
|
import os from 'node:os';
|
|
7
9
|
import path from 'node:path';
|
|
8
|
-
import { execFile } from 'node:child_process';
|
|
10
|
+
import { execFile, spawnSync } from 'node:child_process';
|
|
9
11
|
import { promisify } from 'node:util';
|
|
10
|
-
import { createInterface } from 'node:readline/promises';
|
|
11
12
|
import { createRequire } from 'node:module';
|
|
12
13
|
|
|
13
14
|
const run = promisify(execFile);
|
|
@@ -15,77 +16,122 @@ const require = createRequire(import.meta.url);
|
|
|
15
16
|
const pkg = require('../package.json');
|
|
16
17
|
const NAME = 'hwpx-bridge';
|
|
17
18
|
const isWin = process.platform === 'win32';
|
|
19
|
+
const home = () => os.homedir();
|
|
18
20
|
|
|
19
21
|
/** Pinned so a client never silently runs a newer (or hijacked) npm release. */
|
|
20
22
|
export function packageSpec(version = pkg.version) {
|
|
21
23
|
return /^\d+\.\d+\.\d+$/.test(version) ? `${NAME}@${version}` : NAME;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/**
|
|
26
|
+
/** Apps on Windows cannot start npx.cmd directly, hence `cmd /c`. */
|
|
25
27
|
export function serverEntry() {
|
|
26
28
|
return isWin
|
|
27
29
|
? { command: 'cmd', args: ['/c', 'npx', '-y', packageSpec(), 'mcp'] }
|
|
28
30
|
: { command: 'npx', args: ['-y', packageSpec(), 'mcp'] };
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
function onPath(command) {
|
|
34
|
+
const result = spawnSync(isWin ? 'where' : 'which', [command], { stdio: 'ignore', windowsHide: true });
|
|
35
|
+
return result.status === 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const exists = file => fss.existsSync(file);
|
|
39
|
+
|
|
31
40
|
function claudeDesktopConfig() {
|
|
32
|
-
const home = os.homedir();
|
|
33
41
|
if (isWin) {
|
|
34
|
-
const appData = process.env.APPDATA ?? path.join(home, 'AppData/Roaming');
|
|
35
|
-
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, 'AppData/Local');
|
|
42
|
+
const appData = process.env.APPDATA ?? path.join(home(), 'AppData/Roaming');
|
|
43
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(home(), 'AppData/Local');
|
|
36
44
|
// The Microsoft Store build of Claude reads a package-local copy of %APPDATA%.
|
|
37
45
|
const store = path.join(localAppData, 'Packages/Claude_pzs8sxrjxfjjc/LocalCache/Roaming/Claude');
|
|
38
|
-
return path.join(
|
|
46
|
+
return path.join(exists(store) ? store : path.join(appData, 'Claude'), 'claude_desktop_config.json');
|
|
39
47
|
}
|
|
40
|
-
if (process.platform === 'darwin') return path.join(home, 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
41
|
-
return path.join(home, '.config/Claude/claude_desktop_config.json');
|
|
48
|
+
if (process.platform === 'darwin') return path.join(home(), 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
49
|
+
return path.join(home(), '.config/Claude/claude_desktop_config.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function claudeDesktopInstalled() {
|
|
53
|
+
if (exists(path.dirname(claudeDesktopConfig()))) return true;
|
|
54
|
+
if (isWin) return exists(path.join(process.env.LOCALAPPDATA ?? path.join(home(), 'AppData/Local'), 'AnthropicClaude'));
|
|
55
|
+
if (process.platform === 'darwin') return exists('/Applications/Claude.app');
|
|
56
|
+
return false;
|
|
42
57
|
}
|
|
43
58
|
|
|
59
|
+
const codexHome = () => process.env.CODEX_HOME || path.join(home(), '.codex');
|
|
60
|
+
|
|
44
61
|
export const CLIENTS = [
|
|
45
|
-
{ id: 'claude-desktop', label: 'Claude 앱
|
|
46
|
-
{ id: 'claude-code', label: 'Claude Code
|
|
47
|
-
{ id: 'codex', label: 'Codex (ChatGPT)',
|
|
48
|
-
{ id: 'cursor', label: 'Cursor',
|
|
49
|
-
{ id: 'gemini', label: 'Gemini CLI',
|
|
50
|
-
{ id: 'windsurf', label: 'Windsurf',
|
|
62
|
+
{ id: 'claude-desktop', label: 'Claude 앱', found: claudeDesktopInstalled, register: () => registerJson(claudeDesktopConfig()) },
|
|
63
|
+
{ id: 'claude-code', label: 'Claude Code', found: () => onPath('claude'), register: registerClaudeCode },
|
|
64
|
+
{ id: 'codex', label: 'Codex (ChatGPT 데스크톱·CLI·IDE)', found: () => exists(codexHome()) || onPath('codex'), register: registerCodex },
|
|
65
|
+
{ id: 'cursor', label: 'Cursor', found: () => exists(path.join(home(), '.cursor')), register: () => registerJson(path.join(home(), '.cursor/mcp.json')) },
|
|
66
|
+
{ id: 'gemini', label: 'Gemini CLI', found: () => exists(path.join(home(), '.gemini')), register: () => registerJson(path.join(home(), '.gemini/settings.json')) },
|
|
67
|
+
{ id: 'windsurf', label: 'Windsurf', found: () => exists(path.join(home(), '.codeium/windsurf')), register: () => registerJson(path.join(home(), '.codeium/windsurf/mcp_config.json')) }
|
|
51
68
|
];
|
|
52
69
|
|
|
70
|
+
async function backup(file) {
|
|
71
|
+
if (exists(file)) await fs.copyFile(file, `${file}.bak-${Date.now()}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
async function registerJson(file) {
|
|
54
75
|
let config = {};
|
|
55
|
-
if (
|
|
76
|
+
if (exists(file)) {
|
|
56
77
|
const raw = (await fs.readFile(file, 'utf8')).replace(/^/, '');
|
|
57
78
|
config = raw.trim() ? JSON.parse(raw) : {};
|
|
58
79
|
}
|
|
59
80
|
if (typeof config !== 'object' || config === null || Array.isArray(config)) throw new Error(`설정 파일 형식이 올바르지 않아 그대로 두었습니다: ${file}`);
|
|
60
81
|
const entry = serverEntry();
|
|
61
82
|
if (JSON.stringify(config.mcpServers?.[NAME]) === JSON.stringify(entry)) return `이미 등록됨 (${file})`;
|
|
62
|
-
|
|
83
|
+
await backup(file);
|
|
63
84
|
config.mcpServers = { ...(config.mcpServers ?? {}), [NAME]: entry };
|
|
64
85
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
65
86
|
await fs.writeFile(file, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
66
87
|
return `등록 완료 (${file})`;
|
|
67
88
|
}
|
|
68
89
|
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
/** Codex keeps MCP servers in config.toml, shared by the Codex app, CLI and IDE extension. */
|
|
91
|
+
export function codexBlock() {
|
|
71
92
|
const entry = serverEntry();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
return [
|
|
94
|
+
`[mcp_servers.${NAME}]`,
|
|
95
|
+
`command = ${JSON.stringify(entry.command)}`,
|
|
96
|
+
`args = [${entry.args.map(a => JSON.stringify(a)).join(', ')}]`,
|
|
97
|
+
'startup_timeout_sec = 180 # first start downloads the engines',
|
|
98
|
+
'tool_timeout_sec = 1800 # OCR model download, long conversions',
|
|
99
|
+
''
|
|
100
|
+
].join('\n');
|
|
77
101
|
}
|
|
78
102
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
}
|
|
103
|
+
/** Replace an existing [mcp_servers.hwpx-bridge] table (and its sub-tables) or append one. */
|
|
104
|
+
export function mergeCodexToml(text) {
|
|
105
|
+
const header = /^\s*\[\s*mcp_servers\s*\.\s*(?:"hwpx-bridge"|hwpx-bridge)\s*(?:\.[^\]]*)?\]\s*(#.*)?$/;
|
|
106
|
+
const lines = text.replace(/^/, '').split(/\r?\n/);
|
|
107
|
+
const kept = [];
|
|
108
|
+
let skipping = false;
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
if (/^\s*\[/.test(line)) skipping = header.test(line);
|
|
111
|
+
if (!skipping) kept.push(line);
|
|
112
|
+
}
|
|
113
|
+
const body = kept.join('\n').replace(/\s+$/, '');
|
|
114
|
+
return (body ? `${body}\n\n` : '') + codexBlock();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function registerCodex() {
|
|
118
|
+
const file = path.join(codexHome(), 'config.toml');
|
|
119
|
+
const before = exists(file) ? await fs.readFile(file, 'utf8') : '';
|
|
120
|
+
const after = mergeCodexToml(before);
|
|
121
|
+
if (before.replace(/\r\n/g, '\n') === after) return `이미 등록됨 (${file})`;
|
|
122
|
+
await backup(file);
|
|
123
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
124
|
+
await fs.writeFile(file, after, 'utf8');
|
|
125
|
+
return `등록 완료 (${file})`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function registerClaudeCode() {
|
|
129
|
+
const options = { windowsHide: true, timeout: 60_000, shell: isWin }; // claude is a .cmd shim on Windows
|
|
130
|
+
const entry = serverEntry();
|
|
131
|
+
// Replace an earlier registration (e.g. an older pinned version).
|
|
132
|
+
await run('claude', ['mcp', 'remove', '--scope', 'user', NAME], options).catch(() => {});
|
|
133
|
+
await run('claude', ['mcp', 'add', '--scope', 'user', NAME, '--', entry.command, ...entry.args], options);
|
|
134
|
+
return '등록 완료 (claude mcp add --scope user)';
|
|
89
135
|
}
|
|
90
136
|
|
|
91
137
|
export async function runSetup(argv) {
|
|
@@ -95,25 +141,33 @@ export async function runSetup(argv) {
|
|
|
95
141
|
const ids = (argv[flag + 1] ?? '').split(',').filter(Boolean);
|
|
96
142
|
clients = ids.map(id => CLIENTS.find(c => c.id === id));
|
|
97
143
|
if (!ids.length || clients.some(c => !c)) throw new Error(`--client 값: ${CLIENTS.map(c => c.id).join(', ')}`);
|
|
98
|
-
} else
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
} else {
|
|
145
|
+
clients = CLIENTS.filter(client => client.found());
|
|
146
|
+
const missing = CLIENTS.filter(client => !clients.includes(client)).map(client => client.label);
|
|
147
|
+
console.log(`\nhwpx-bridge ${pkg.version}: 이 PC에서 찾은 AI 앱 모두에 등록합니다.`);
|
|
148
|
+
if (missing.length) console.log(` (설치되지 않아 건너뜀: ${missing.join(', ')})`);
|
|
149
|
+
if (!clients.length) {
|
|
150
|
+
console.log('\n등록할 AI 앱을 찾지 못했습니다. 앱을 설치한 뒤 다시 실행하거나, 아래 설정을 앱에 직접 넣으세요.\n');
|
|
151
|
+
printConfig();
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
console.log('');
|
|
101
157
|
let failed = false;
|
|
102
158
|
for (const client of clients) {
|
|
103
159
|
try {
|
|
104
|
-
|
|
105
|
-
console.log(` + ${client.label}: ${message}`);
|
|
160
|
+
console.log(` + ${client.label}: ${await client.register()}`);
|
|
106
161
|
} catch (error) {
|
|
107
162
|
failed = true;
|
|
108
|
-
|
|
109
|
-
console.log(` x ${client.label}: ${hint}`);
|
|
163
|
+
console.log(` x ${client.label}: ${error.code === 'ENOENT' ? '명령을 찾을 수 없습니다.' : error.message.split('\n')[0]}`);
|
|
110
164
|
}
|
|
111
165
|
}
|
|
112
|
-
console.log(
|
|
113
|
-
console.log(`직접 설정하려면: npx -y ${packageSpec()} config`);
|
|
166
|
+
console.log('\n등록한 앱을 완전히 종료했다가 다시 열면 hwpx-bridge 도구(kordoc 전체 + filee_convert)가 모든 대화에서 보입니다.');
|
|
114
167
|
if (failed) process.exitCode = 1;
|
|
115
168
|
}
|
|
116
169
|
|
|
117
170
|
export function printConfig() {
|
|
118
171
|
console.log(JSON.stringify({ mcpServers: { [NAME]: serverEntry() } }, null, 2));
|
|
172
|
+
console.log(`\nCodex (~/.codex/config.toml):\n\n${codexBlock()}`);
|
|
119
173
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hwpx-bridge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "One MCP server for Korean documents: every kordoc tool (read/write/edit HWP·HWPX·PDF, OCR) plus Filee document conversion (DOCX→HWPX with formatting). One-line setup for Claude, Codex, Cursor and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "hwpx-bridge": "bin/hwpx-bridge.mjs" },
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"kordoc": "4.24.62"
|
|
12
12
|
},
|
|
13
13
|
"optionalDependencies": {
|
|
14
|
-
"hwpx-bridge-filee-win32-x64": "0.3.
|
|
15
|
-
"hwpx-bridge-filee-linux-x64": "0.3.
|
|
14
|
+
"hwpx-bridge-filee-win32-x64": "0.3.3",
|
|
15
|
+
"hwpx-bridge-filee-linux-x64": "0.3.3"
|
|
16
16
|
},
|
|
17
17
|
"keywords": ["hwp", "hwpx", "docx", "mcp", "kordoc", "filee", "korean", "ocr", "claude", "codex"],
|
|
18
18
|
"license": "MIT",
|