fix-vietnamese-claude-code 1.0.2 → 1.0.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 +16 -45
- package/package.json +1 -1
- package/patch-cli-claude-code.js +219 -94
package/README.md
CHANGED
|
@@ -1,75 +1,46 @@
|
|
|
1
1
|
# Claude Code Vietnamese IME Fix
|
|
2
2
|
|
|
3
|
-
Fix lỗi gõ tiếng Việt trong Claude Code CLI
|
|
3
|
+
Fix lỗi gõ tiếng Việt trong Claude Code CLI cho các bộ gõ (OpenKey, EVKey, PHTV, Unikey...). Hỗ trợ cả phiên bản **npm** và **binary** (macOS, Windows, Linux).
|
|
4
4
|
|
|
5
|
-
**Phiên bản đã test:**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Khi gõ tiếng Việt trong Claude Code CLI, các bộ gõ sử dụng kỹ thuật "backspace rồi thay thế" để chuyển đổi ký tự (ví dụ: `a` + `s` → `á`). Claude Code xử lý phần backspace (ký tự DEL `\x7f`) nhưng không đưa ký tự thay thế vào đúng vị trí, dẫn đến:
|
|
10
|
-
|
|
11
|
-
- Ký tự bị "nuốt" hoặc mất khi gõ.
|
|
12
|
-
- Văn bản hiển thị không đúng với những gì đã gõ.
|
|
13
|
-
- Gây khó khăn khi nhập liệu trực tiếp trong terminal.
|
|
14
|
-
|
|
15
|
-
Script này patch tệp `cli.js` của Claude Code để xử lý đúng các ký tự tiếng Việt sau khi nhận tín hiệu xóa từ bộ gõ.
|
|
5
|
+
**Phiên bản đã test:**
|
|
6
|
+
- npm: v2.1.39
|
|
7
|
+
- binary: v2.1.39
|
|
8
|
+
(Chi tiết tại [CHANGELOG.md](./CHANGELOG.md))
|
|
16
9
|
|
|
17
10
|
## Cài đặt & Sử dụng
|
|
18
11
|
|
|
19
|
-
|
|
20
|
-
> **Yêu cầu:** Chỉ hỗ trợ phiên bản cài đặt qua **npm**. Nếu bạn cài Claude Code qua các đường dẫn khác (MSI installer, Homebrew binary), vui lòng gỡ cài đặt và cài lại qua npm:
|
|
21
|
-
> ```bash
|
|
22
|
-
> npm install -g @anthropic-ai/claude-code
|
|
23
|
-
> ```
|
|
24
|
-
|
|
25
|
-
### 2. Chạy patch
|
|
26
|
-
|
|
27
|
-
Bạn có thể chạy trực tiếp bằng **npx** (không cần tải file):
|
|
12
|
+
Gõ lệnh sau trong terminal để áp dụng bản vá:
|
|
28
13
|
|
|
29
14
|
```bash
|
|
30
15
|
npx fix-vietnamese-claude-code
|
|
31
16
|
```
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
node patch-cli-claude-code.js
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Script sẽ tự động tìm kiếm đường dẫn đến tệp `cli.js` của Claude Code trên hệ thống của bạn và áp dụng bản vá.
|
|
18
|
+
Lệnh trên sẽ tự động tìm và vá tệp `cli.js` (npm) hoặc file binary của Claude Code trên hệ thống của bạn.
|
|
40
19
|
|
|
41
20
|
### Tùy chọn nâng cao
|
|
42
21
|
|
|
43
|
-
Nếu
|
|
22
|
+
Nếu cài đặt ở đường dẫn không mặc định, bạn có thể chỉ định thủ công:
|
|
44
23
|
|
|
45
24
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
25
|
+
# Đối với bản npm cli.js
|
|
26
|
+
npx fix-vietnamese-claude-code -f "/đường/dẫn/đến/cli.js"
|
|
48
27
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
node patch-cli-claude-code.js --help
|
|
28
|
+
# Đối với bản binary
|
|
29
|
+
npx fix-vietnamese-claude-code -f "/đường/dẫn/đến/claude"
|
|
52
30
|
```
|
|
53
31
|
|
|
54
32
|
## Lưu ý
|
|
33
|
+
- **Cập nhật:** Bạn cần chạy lại lệnh patch mỗi khi Claude Code cập nhật phiên bản mới.
|
|
34
|
+
- **Môi trường:** Đã kiểm tra tốt trên Windows (CMD/PowerShell), macOS và Linux.
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Phát triển (Dành cho Developer)
|
|
60
|
-
|
|
61
|
-
Dự án sử dụng **Vitest** để kiểm tra tính đúng đắn của bản vá trên nhiều phiên bản Claude Code khác nhau.
|
|
36
|
+
## Phát triển
|
|
37
|
+
Dự án sử dụng **Vitest** để kiểm tra tính đúng đắn trên nhiều phiên bản.
|
|
62
38
|
|
|
63
39
|
```bash
|
|
64
|
-
# Cài đặt dependencies
|
|
65
40
|
npm install
|
|
66
|
-
|
|
67
|
-
# Chạy test
|
|
68
41
|
npm test
|
|
69
42
|
```
|
|
70
43
|
|
|
71
|
-
Script test sẽ tự động tải các phiên bản thực tế của Claude Code từ npm (từ `2.0.64` trở đi) để đảm bảo regex luôn khớp.
|
|
72
|
-
|
|
73
44
|
## Credits
|
|
74
45
|
|
|
75
46
|
Dự án tham khảo và cải tiến từ:
|
package/package.json
CHANGED
package/patch-cli-claude-code.js
CHANGED
|
@@ -5,37 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
* Original method stolen from this
|
|
10
|
-
* https://github.com/manhit96/claude-code-vietnamese-fix
|
|
11
|
-
* This patch fixes Vietnamese input method editors (IMEs) compatibility issues in Claude.
|
|
12
|
-
* By modifying `cli.js` code from this:
|
|
13
|
-
* ```js
|
|
14
|
-
* if (!S.equals(CA)) {
|
|
15
|
-
* if (S.text !== CA.text) Q(CA.text);
|
|
16
|
-
* T(CA.offset)
|
|
17
|
-
* }
|
|
18
|
-
* ct1(), lt1();
|
|
19
|
-
* return
|
|
20
|
-
* ```
|
|
21
|
-
* To this:
|
|
22
|
-
* ```js
|
|
23
|
-
* /* Vietnamese IME fix * /
|
|
24
|
-
* let _vn = n.replace(/\x7f/g, "");
|
|
25
|
-
* if (_vn.length > 0) {
|
|
26
|
-
* for (const _c of _vn) CA = CA.insert(_c);
|
|
27
|
-
* if (!S.equals(CA)) {
|
|
28
|
-
* if (S.text !== CA.text) Q(CA.text);
|
|
29
|
-
* T(CA.offset)
|
|
30
|
-
* }
|
|
31
|
-
* }
|
|
32
|
-
* Oe1(), Me1();
|
|
33
|
-
* return
|
|
34
|
-
* ```
|
|
35
|
-
* Note: I have no idea how this works, just ported python logic to JS with make it better performance.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
const DORK = '/* Vietnamese IME fix */';
|
|
8
|
+
const DORK = '/* _0x0a0d_ime_fix_ */';
|
|
39
9
|
|
|
40
10
|
function usage() {
|
|
41
11
|
console.log(`
|
|
@@ -43,112 +13,261 @@ Usage:
|
|
|
43
13
|
fix-claude-code-vn [options]
|
|
44
14
|
|
|
45
15
|
Options:
|
|
46
|
-
-f, --file <
|
|
16
|
+
-f, --file <_path_> Path to cli.js or claude file
|
|
17
|
+
-d, --dry-run Test without overwriting the file
|
|
18
|
+
-o, --output <path> Write patched content to a new file
|
|
47
19
|
-h, --help Show this help message
|
|
48
20
|
|
|
49
21
|
Description:
|
|
50
|
-
This script patches Claude Code
|
|
22
|
+
This script patches Claude Code CLI tool to fix Vietnamese IME issues.
|
|
51
23
|
If no file is specified, it will try to find it automatically.
|
|
52
24
|
`);
|
|
53
25
|
}
|
|
54
26
|
|
|
55
27
|
function findClaudePath() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
28
|
+
const isWin = os.platform() === "win32";
|
|
29
|
+
|
|
30
|
+
const run = (cmd) => {
|
|
31
|
+
try {
|
|
32
|
+
return execSync(cmd, { stdio: ["ignore", "pipe", "ignore"] })
|
|
33
|
+
.toString()
|
|
34
|
+
.split(/\r?\n/)[0]
|
|
35
|
+
.trim();
|
|
36
|
+
} catch {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const exists = (p) => p && fs.existsSync(p);
|
|
42
|
+
|
|
43
|
+
// 1) which / where / bun which
|
|
44
|
+
for (const cmd of [
|
|
45
|
+
isWin ? "where claude" : "which claude",
|
|
46
|
+
"bun which claude",
|
|
47
|
+
]) {
|
|
48
|
+
const p = run(cmd);
|
|
49
|
+
if (exists(p)) {
|
|
50
|
+
if (!isWin) {
|
|
64
51
|
try {
|
|
65
|
-
|
|
66
|
-
} catch
|
|
52
|
+
return execSync(`realpath "${ p }"`).toString().trim();
|
|
53
|
+
} catch {}
|
|
67
54
|
}
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
// 2) Bun global paths
|
|
60
|
+
const bunInstall =
|
|
61
|
+
process.env.BUN_INSTALL ||
|
|
62
|
+
(isWin
|
|
63
|
+
? path.join(process.env.USERPROFILE || "", ".bun")
|
|
64
|
+
: path.join(process.env.HOME || "", ".bun"));
|
|
65
|
+
|
|
66
|
+
const bunPaths = [
|
|
67
|
+
path.join(bunInstall, "bin", isWin ? "claude.exe" : "claude"),
|
|
68
|
+
path.join(bunInstall, "bin", isWin ? "claude.cmd" : "claude"),
|
|
69
|
+
path.join(
|
|
70
|
+
bunInstall,
|
|
71
|
+
"install",
|
|
72
|
+
"global",
|
|
73
|
+
"node_modules",
|
|
74
|
+
"@anthropic-ai",
|
|
75
|
+
"claude-code",
|
|
76
|
+
"cli.js"
|
|
77
|
+
),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const p of bunPaths) {
|
|
81
|
+
if (exists(p)) {
|
|
82
|
+
return p;
|
|
75
83
|
}
|
|
76
|
-
}
|
|
84
|
+
}
|
|
77
85
|
|
|
78
|
-
//
|
|
86
|
+
// 3) npm global
|
|
79
87
|
try {
|
|
80
|
-
const npmRoot = execSync(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
const npmRoot = execSync("npm root -g").toString().trim();
|
|
89
|
+
|
|
90
|
+
const cliPath = path.join(
|
|
91
|
+
npmRoot,
|
|
92
|
+
"@anthropic-ai",
|
|
93
|
+
"claude-code",
|
|
94
|
+
"cli.js"
|
|
95
|
+
);
|
|
96
|
+
if (exists(cliPath)) {
|
|
97
|
+
|
|
98
|
+
return cliPath;
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 4) Windows fallbacks
|
|
105
|
+
if (isWin) {
|
|
106
|
+
|
|
107
|
+
const paths = [
|
|
108
|
+
path.join(
|
|
109
|
+
process.env.APPDATA || "",
|
|
110
|
+
"npm",
|
|
111
|
+
"node_modules",
|
|
112
|
+
"@anthropic-ai",
|
|
113
|
+
"claude-code",
|
|
114
|
+
"cli.js"
|
|
115
|
+
),
|
|
116
|
+
path.join(
|
|
117
|
+
process.env.LOCALAPPDATA || "",
|
|
118
|
+
"npm",
|
|
119
|
+
"node_modules",
|
|
120
|
+
"@anthropic-ai",
|
|
121
|
+
"claude-code",
|
|
122
|
+
"cli.js"
|
|
123
|
+
),
|
|
90
124
|
];
|
|
91
|
-
|
|
92
|
-
// nvm-windows
|
|
125
|
+
|
|
93
126
|
if (process.env.NVM_HOME) {
|
|
94
127
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
for (const d of fs.readdirSync(process.env.NVM_HOME)) {
|
|
129
|
+
paths.push(
|
|
130
|
+
path.join(
|
|
131
|
+
process.env.NVM_HOME,
|
|
132
|
+
d,
|
|
133
|
+
"node_modules",
|
|
134
|
+
"@anthropic-ai",
|
|
135
|
+
"claude-code",
|
|
136
|
+
"cli.js"
|
|
137
|
+
)
|
|
138
|
+
);
|
|
99
139
|
}
|
|
100
|
-
} catch (e) {
|
|
140
|
+
} catch (e) {
|
|
141
|
+
|
|
142
|
+
}
|
|
101
143
|
}
|
|
102
144
|
|
|
103
|
-
for (const p of
|
|
104
|
-
if (
|
|
145
|
+
for (const p of paths) {
|
|
146
|
+
if (exists(p)) {
|
|
147
|
+
|
|
148
|
+
return p;
|
|
149
|
+
}
|
|
105
150
|
}
|
|
106
151
|
}
|
|
107
152
|
|
|
153
|
+
|
|
108
154
|
return null;
|
|
109
155
|
}
|
|
110
156
|
|
|
111
|
-
|
|
157
|
+
// stolen fixed solution from manhit96/claude-code-vietnamese-fix
|
|
158
|
+
function patchContentJs(fileContent) {
|
|
112
159
|
if (fileContent.includes(DORK)) {
|
|
113
|
-
return { success: true, alreadyPatched: true
|
|
160
|
+
return { success: true, alreadyPatched: true };
|
|
114
161
|
}
|
|
115
162
|
|
|
116
163
|
// Pattern matching:
|
|
117
164
|
// match this: l.match(/\x7f/g)...if(!S.equals(CA)){if(S.text!==CA.text)Q(CA.text);T(CA.offset)}ct1(),lt1();return
|
|
118
165
|
// We use a regex that captures variable and function names dynamically.
|
|
119
|
-
const re = /((?<var0>[\w$]+)\.match\(\/\\x7f\/g\).*?)if\(!(?<var1>[\w$]+)\.equals\((?<var2>[\w$]+)\)\){if\(\k<var1>\.text!==\k<var2>\.text\)(?<func1>[\w$]+)\(\k<var2>\.text\);(?<func2>[\w$]+)\(\k<var2>\.offset\)}(?<
|
|
120
|
-
|
|
121
|
-
const newContent = fileContent.replace(re, (
|
|
166
|
+
const re = /(?<m0>(?<var0>[\w$]+)\.match\(\/\\x7f\/g\).*?)(?<m1>if\(!(?<var1>[\w$]+)\.equals\((?<var2>[\w$]+)\)\){if\(\k<var1>\.text!==\k<var2>\.text\)(?<func1>[\w$]+)\(\k<var2>\.text\);(?<func2>[\w$]+)\(\k<var2>\.offset\)})(?<m2>(?:[\w$]+\(\),?\s*)*;?\s*return)/g;
|
|
167
|
+
|
|
168
|
+
const newContent = fileContent.replace(re, (...args) => {
|
|
169
|
+
const { m0, m1, var0, var2, m2 } = args[args.length - 1];
|
|
122
170
|
return `
|
|
123
171
|
${DORK}
|
|
124
172
|
${m0}
|
|
125
173
|
let _vn = ${var0}.replace(/\\x7f/g, "");
|
|
126
174
|
if (_vn.length > 0) {
|
|
127
175
|
for (const _c of _vn) ${var2} = ${var2}.insert(_c);
|
|
128
|
-
|
|
129
|
-
if (${var1}.text !== ${var2}.text) ${func1}(${var2}.text);
|
|
130
|
-
${func2}(${var2}.offset);
|
|
131
|
-
}
|
|
176
|
+
${m1}
|
|
132
177
|
}
|
|
133
|
-
${
|
|
178
|
+
${m2}
|
|
134
179
|
`;
|
|
135
180
|
});
|
|
136
181
|
|
|
137
|
-
if (newContent === fileContent) {
|
|
138
|
-
return { success: false,
|
|
182
|
+
if (newContent.length === fileContent.length) {
|
|
183
|
+
return { success: false, message: 'Patch failed: no match found' };
|
|
139
184
|
}
|
|
140
185
|
|
|
141
186
|
return { success: true, alreadyPatched: false, content: newContent };
|
|
142
187
|
}
|
|
143
188
|
|
|
189
|
+
function patchContentBinary(binaryContent) {
|
|
190
|
+
if (binaryContent.includes(DORK)) {
|
|
191
|
+
return { success: true, alreadyPatched: true };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const re = /(?<m0>(?<var0>[\w$]+)\.match\(\/\\x7f\/g\).*?)(?<m1>if\(!(?<var1>[\w$]+)\.equals\((?<var2>[\w$]+)\)\){if\(\k<var1>\.text!==\k<var2>\.text\)(?<func1>[\w$]+)\(\k<var2>\.text\);(?<func2>[\w$]+)\(\k<var2>\.offset\)})(?<m2>(?:[\w$]+\(\),?\s*)*;?\s*return)/g;
|
|
195
|
+
|
|
196
|
+
const matches = [];
|
|
197
|
+
binaryContent = binaryContent.replace(re, (...args) => {
|
|
198
|
+
const groups = args[args.length - 1];
|
|
199
|
+
const offset = args[args.length - 3];
|
|
200
|
+
const { m0, m1, var0, var2, m2 } = groups;
|
|
201
|
+
|
|
202
|
+
const patchedContent = `${DORK}
|
|
203
|
+
${m0}
|
|
204
|
+
let _vn = ${var0}.replace(/\\x7f/g, "");
|
|
205
|
+
if (_vn.length > 0) {
|
|
206
|
+
for (const _c of _vn) ${var2} = ${var2}.insert(_c);
|
|
207
|
+
${m1}
|
|
208
|
+
}
|
|
209
|
+
${m2}`.replace(/^\s+/gm, '');
|
|
210
|
+
matches.push({ diff: patchedContent.length - args[0].length, index: offset });
|
|
211
|
+
return patchedContent;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (matches.length === 0) {
|
|
215
|
+
return { success: false, message: 'Patch failed: no match found' };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// now from index, we must look back for `\x00// @bun `
|
|
219
|
+
const pragma = `// @bun `
|
|
220
|
+
const pragmaLength = pragma.length
|
|
221
|
+
for (let i = 0; i < matches.length; i++) {
|
|
222
|
+
for (let j = matches[i].index - 1; j >= (i === 0 ? 0 : matches[i - 1].index); j--) {
|
|
223
|
+
if (binaryContent[j] === '\x00') {
|
|
224
|
+
// pragma test
|
|
225
|
+
if (binaryContent.slice(j + 1, j + 1 + pragmaLength).toString() === pragma) {
|
|
226
|
+
// look next to find first `\n//`
|
|
227
|
+
let found = false;
|
|
228
|
+
for (let k = j + 1 + pragmaLength; k < matches[i].index; k++) {
|
|
229
|
+
if (binaryContent[k] === '\n' && binaryContent[k + 1] === '/' && binaryContent[k + 2] === '/') {
|
|
230
|
+
// remove from binaryContent after `//` exactly `diff` bytes
|
|
231
|
+
const diff = matches[i].diff;
|
|
232
|
+
const sliceStart = k + 3;
|
|
233
|
+
binaryContent = binaryContent.slice(0, sliceStart) + binaryContent.slice(sliceStart + diff);
|
|
234
|
+
found = true;
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (found) {
|
|
239
|
+
matches[i].found = true;
|
|
240
|
+
break;
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!matches[i].found) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (matches.every(m => !m.found)) {
|
|
251
|
+
return { success: false, message: 'Patch failed: pragma' };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { success: true, alreadyPatched: false, content: binaryContent };
|
|
255
|
+
}
|
|
256
|
+
|
|
144
257
|
// Main execution
|
|
145
258
|
if (require.main === module) {
|
|
146
259
|
let targetPath = null;
|
|
260
|
+
let isDryRun = false;
|
|
261
|
+
let outputPath = null;
|
|
147
262
|
const args = process.argv.slice(2);
|
|
148
263
|
|
|
149
264
|
for (let i = 0; i < args.length; i++) {
|
|
150
265
|
if (args[i] === '-f' || args[i] === '--file') {
|
|
151
266
|
targetPath = args[++i];
|
|
267
|
+
} else if (args[i] === '-d' || args[i] === '--dry-run') {
|
|
268
|
+
isDryRun = true;
|
|
269
|
+
} else if (args[i] === '-o' || args[i] === '--output') {
|
|
270
|
+
outputPath = args[++i];
|
|
152
271
|
} else if (args[i] === '-h' || args[i] === '--help') {
|
|
153
272
|
usage();
|
|
154
273
|
process.exit(0);
|
|
@@ -160,7 +279,7 @@ if (require.main === module) {
|
|
|
160
279
|
}
|
|
161
280
|
|
|
162
281
|
if (!targetPath || !fs.existsSync(targetPath)) {
|
|
163
|
-
console.error('Error: Could not find Claude Code
|
|
282
|
+
console.error('Error: Could not find Claude Code CLI.');
|
|
164
283
|
if (targetPath) console.error(`Path tried: ${targetPath}`);
|
|
165
284
|
usage();
|
|
166
285
|
process.exit(1);
|
|
@@ -168,13 +287,9 @@ if (require.main === module) {
|
|
|
168
287
|
|
|
169
288
|
console.log(`Target: ${targetPath}`);
|
|
170
289
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const content = fs.readFileSync(targetPath, 'utf-8');
|
|
177
|
-
const result = patchContent(content);
|
|
290
|
+
const result = targetPath.endsWith('.js')
|
|
291
|
+
? patchContentJs(fs.readFileSync(targetPath, 'latin1'))
|
|
292
|
+
: patchContentBinary(fs.readFileSync(targetPath, 'latin1'));
|
|
178
293
|
|
|
179
294
|
if (result.alreadyPatched) {
|
|
180
295
|
console.log('Claude is already patched for Vietnamese IME.');
|
|
@@ -182,13 +297,23 @@ if (require.main === module) {
|
|
|
182
297
|
}
|
|
183
298
|
|
|
184
299
|
if (!result.success) {
|
|
185
|
-
console.error(
|
|
300
|
+
console.error(result.message);
|
|
186
301
|
process.exit(1);
|
|
187
302
|
}
|
|
188
303
|
|
|
189
|
-
|
|
190
|
-
|
|
304
|
+
if (isDryRun) {
|
|
305
|
+
console.log('Dry run: patch applied successfully (not saved).');
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const finalPath = outputPath || targetPath;
|
|
310
|
+
fs.writeFileSync(finalPath, result.content, 'latin1');
|
|
311
|
+
console.log(`Success: Claude has been patched at ${finalPath}`);
|
|
191
312
|
}
|
|
192
313
|
|
|
193
314
|
// Export for testing
|
|
194
|
-
module.exports = {
|
|
315
|
+
module.exports = {
|
|
316
|
+
DORK,
|
|
317
|
+
patchContentJs,
|
|
318
|
+
patchContentBinary,
|
|
319
|
+
};
|