polished-localization-for-claude-code 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/install.js ADDED
@@ -0,0 +1,813 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const os = require('os');
7
+ const pkg = require('../package.json');
8
+
9
+ const MAGENTA = '\x1b[38;5;206m';
10
+ const GREEN = '\x1b[0;32m';
11
+ const YELLOW = '\x1b[0;33m';
12
+ const RED = '\x1b[0;31m';
13
+ const CYAN = '\x1b[0;36m';
14
+ const NC = '\x1b[0m';
15
+ const IS_WIN = process.platform === 'win32';
16
+
17
+ console.log(`\n${MAGENTA}==============================================${NC}`);
18
+ console.log(`${MAGENTA} Polished Localization for Claude Code 安装器${NC}`);
19
+ console.log(`${MAGENTA}==============================================${NC}\n`);
20
+
21
+ // 路径常量
22
+ const homeDir = os.homedir();
23
+ const claudeDir = path.join(homeDir, '.claude');
24
+ const hooksDir = path.join(claudeDir, 'hooks');
25
+ const localizeDir = path.join(claudeDir, 'localize');
26
+ const settingsFile = path.join(claudeDir, 'settings.json');
27
+
28
+ // 获取 npm 包目录
29
+ let npmDir;
30
+ try {
31
+ npmDir = path.dirname(require.resolve(`${pkg.name}/package.json`));
32
+ } catch (e) {
33
+ npmDir = path.resolve(__dirname, '..');
34
+ }
35
+
36
+ console.log(`${CYAN}包目录: ${npmDir}${NC}`);
37
+ console.log(`${CYAN}系统: ${IS_WIN ? 'Windows' : process.platform}${NC}`);
38
+ console.log(`${CYAN}目标: ${claudeDir}${NC}\n`);
39
+
40
+ // ========== 工具函数 ==========
41
+ function ensureDir(dir) {
42
+ if (!fs.existsSync(dir)) {
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ console.log(`${GREEN}已创建: ${dir}${NC}`);
45
+ }
46
+ }
47
+
48
+ function copyFile(src, dest) {
49
+ try {
50
+ fs.copyFileSync(src, dest);
51
+ console.log(`${GREEN}已复制: ${path.basename(dest)}${NC}`);
52
+ return true;
53
+ } catch (err) {
54
+ console.log(`${RED}复制失败: ${path.basename(dest)} - ${err.message}${NC}`);
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * 将文件内容确保为 LF 换行(bash 脚本必需)
61
+ * npm install 在 Windows 上可能把 LF 转为 CRLF
62
+ * 返回 true 表示文件是干净的(LF only)
63
+ */
64
+ function ensureLfLineEndings(filePath) {
65
+ try {
66
+ // 读取原始 buffer,避免 Node.js 自动转换
67
+ const buf = fs.readFileSync(filePath);
68
+ const hasCR = buf.includes(Buffer.from('\r\n'));
69
+
70
+ if (hasCR) {
71
+ let content = buf.toString('utf8');
72
+ content = content.replace(/\r\n/g, '\n');
73
+ // 确保不以 \r\n 结尾
74
+ if (content.endsWith('\r\n')) {
75
+ content = content.slice(0, -2) + '\n';
76
+ }
77
+ fs.writeFileSync(filePath, content, 'utf8');
78
+
79
+ // 二次确认:重新读取检查
80
+ const recheck = fs.readFileSync(filePath);
81
+ if (recheck.includes(Buffer.from('\r\n'))) {
82
+ console.log(`${RED}换行符修复失败: ${path.basename(filePath)}${NC}`);
83
+ return false;
84
+ }
85
+ console.log(`${GREEN}已修复换行符: ${path.basename(filePath)} (CRLF -> LF)${NC}`);
86
+ }
87
+ return true;
88
+ } catch (err) {
89
+ console.log(`${RED}换行符检查失败: ${err.message}${NC}`);
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 备份 settings.json
96
+ */
97
+ function backupSettings() {
98
+ if (!fs.existsSync(settingsFile)) return null;
99
+ const backupFile = settingsFile + '.bak';
100
+ try {
101
+ fs.copyFileSync(settingsFile, backupFile);
102
+ console.log(`${GREEN}已备份: settings.json.bak${NC}`);
103
+ return backupFile;
104
+ } catch (err) {
105
+ console.log(`${YELLOW}备份失败: ${err.message}${NC}`);
106
+ return null;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Windows 下查找 Git Bash 路径(与 Claude Code 的 cr8() 逻辑一致)
112
+ */
113
+ function findGitBash() {
114
+ if (!IS_WIN) return null;
115
+
116
+ // 1. 环境变量
117
+ if (process.env.CLAUDE_CODE_GIT_BASH_PATH) {
118
+ if (fs.existsSync(process.env.CLAUDE_CODE_GIT_BASH_PATH)) {
119
+ console.log(`${GREEN}使用环境变量 CLAUDE_CODE_GIT_BASH_PATH${NC}`);
120
+ return process.env.CLAUDE_CODE_GIT_BASH_PATH;
121
+ }
122
+ }
123
+
124
+ // 2. 通过 git.exe 推算
125
+ try {
126
+ const gitPath = execSync('where git.exe', { encoding: 'utf8', timeout: 5000 }).trim().split('\n')[0].trim();
127
+ if (gitPath) {
128
+ const gitDir = path.dirname(gitPath);
129
+ const possibleBash = [
130
+ path.join(gitDir, '..', 'bin', 'bash.exe'),
131
+ path.join(gitDir, '..', '..', 'bin', 'bash.exe'),
132
+ path.join(gitDir, 'bash.exe'),
133
+ ];
134
+ for (const p of possibleBash) {
135
+ const resolved = path.resolve(p);
136
+ if (fs.existsSync(resolved)) {
137
+ console.log(`${GREEN}找到 Git Bash: ${resolved}${NC}`);
138
+ return resolved;
139
+ }
140
+ }
141
+ }
142
+ } catch (e) { /* git not found */ }
143
+
144
+ // 3. 常见安装路径
145
+ const commonPaths = [
146
+ 'C:\\Program Files\\Git\\bin\\bash.exe',
147
+ 'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
148
+ path.join(os.homedir(), 'scoop', 'apps', 'git', 'current', 'bin', 'bash.exe'),
149
+ 'D:\\Git\\bin\\bash.exe',
150
+ ];
151
+ for (const p of commonPaths) {
152
+ if (fs.existsSync(p)) {
153
+ console.log(`${GREEN}找到 Git Bash: ${p}${NC}`);
154
+ return p;
155
+ }
156
+ }
157
+
158
+ return null;
159
+ }
160
+
161
+ /**
162
+ * 验证 hook 脚本是否能正常执行
163
+ * 使用 spawnSync 直接通过 stdin 管道传数据,避免 shell 转义问题
164
+ */
165
+ function verifyHook(hookPath) {
166
+ const { spawnSync } = require('child_process');
167
+ const result = { ok: false, output: '', error: '' };
168
+
169
+ try {
170
+ const testInput = JSON.stringify({ tool_name: 'Read', file_path: 'test.py' });
171
+
172
+ let shell, args;
173
+ if (IS_WIN) {
174
+ const bashPath = findGitBash();
175
+ if (!bashPath) {
176
+ result.error = '找不到 Git Bash,无法验证';
177
+ return result;
178
+ }
179
+ shell = bashPath;
180
+ args = [hookPath.replace(/\\/g, '/')];
181
+ } else {
182
+ shell = '/bin/bash';
183
+ args = [hookPath];
184
+ }
185
+
186
+ const proc = spawnSync(shell, args, {
187
+ input: testInput,
188
+ encoding: 'utf8',
189
+ timeout: 10000,
190
+ windowsHide: true,
191
+ });
192
+
193
+ if (proc.error) {
194
+ result.error = `执行失败: ${proc.error.message}`;
195
+ return result;
196
+ }
197
+
198
+ if (proc.status !== 0) {
199
+ result.error = `退出码 ${proc.status}: ${(proc.stderr || '').substring(0, 200)}`;
200
+ return result;
201
+ }
202
+
203
+ const output = (proc.stdout || '').trim();
204
+ result.output = output;
205
+
206
+ // 检查输出是否包含 systemMessage
207
+ if (output.includes('"systemMessage"') && output.includes('test.py')) {
208
+ result.ok = true;
209
+ } else {
210
+ result.error = `输出格式不正确: ${output.substring(0, 200)}`;
211
+ }
212
+ } catch (err) {
213
+ result.error = `执行失败: ${err.message}`;
214
+ }
215
+
216
+ return result;
217
+ }
218
+
219
+ // ========== 安装钩子 ==========
220
+ function installHook() {
221
+ console.log(`${MAGENTA}[1/2] 安装中文提示钩子...${NC}\n`);
222
+
223
+ ensureDir(hooksDir);
224
+
225
+ const hookSrc = path.join(npmDir, 'tool-tips-post.sh');
226
+ const hookDest = path.join(hooksDir, 'tool-tips-post.sh');
227
+
228
+ if (!fs.existsSync(hookSrc)) {
229
+ console.log(`${RED}找不到钩子脚本: ${hookSrc}${NC}`);
230
+ console.log(`${YELLOW}请确认 npm 包完整性${NC}`);
231
+ return false;
232
+ }
233
+
234
+ // 复制 hook 脚本
235
+ if (!copyFile(hookSrc, hookDest)) {
236
+ return false;
237
+ }
238
+
239
+ // 关键:确保 LF 换行(Windows npm install 可能转为 CRLF)
240
+ if (!ensureLfLineEndings(hookDest)) {
241
+ console.log(`${YELLOW}警告: 换行符可能有问题,尝试强制修复...${NC}`);
242
+ // 强制修复:直接从源文件写入
243
+ try {
244
+ const content = fs.readFileSync(hookSrc, 'utf8').replace(/\r\n/g, '\n');
245
+ fs.writeFileSync(hookDest, content, { encoding: 'utf8' });
246
+ console.log(`${GREEN}已强制修复换行符${NC}`);
247
+ } catch (err) {
248
+ console.log(`${RED}强制修复失败: ${err.message}${NC}`);
249
+ }
250
+ }
251
+
252
+ // Unix 设置执行权限
253
+ if (!IS_WIN) {
254
+ try { fs.chmodSync(hookDest, '755'); } catch (err) {}
255
+ }
256
+
257
+ // Windows 特殊检查
258
+ if (IS_WIN) {
259
+ console.log('');
260
+ const bashPath = findGitBash();
261
+ if (!bashPath) {
262
+ console.log(`${RED}${'='.repeat(50)}${NC}`);
263
+ console.log(`${RED} 未找到 Git Bash!${NC}`);
264
+ console.log(`${RED} Claude Code hooks 在 Windows 上需要 Git Bash${NC}`);
265
+ console.log(`${RED} 请安装 Git for Windows:${NC}`);
266
+ console.log(`${RED} https://git-scm.com/downloads/win${NC}`);
267
+ console.log(`${RED}${'='.repeat(50)}${NC}\n`);
268
+ return false;
269
+ }
270
+ }
271
+
272
+ // 更新 settings.json
273
+ if (!updateSettings(hookDest)) {
274
+ return false;
275
+ }
276
+
277
+ // 验证 hook 是否能正常执行
278
+ console.log(`\n${CYAN}验证钩子脚本...${NC}`);
279
+ const verify = verifyHook(hookDest);
280
+ if (verify.ok) {
281
+ console.log(`${GREEN}验证通过! 钩子脚本正常工作${NC}`);
282
+ console.log(`${CYAN}输出示例: ${verify.output.substring(0, 100)}...${NC}`);
283
+ } else {
284
+ console.log(`${RED}验证失败! 钩子脚本可能无法正常工作${NC}`);
285
+ console.log(`${RED}原因: ${verify.error}${NC}`);
286
+ console.log(`${YELLOW}建议: 请运行 polished-localization-install --verify 获取详细诊断${NC}`);
287
+ }
288
+
289
+ return true;
290
+ }
291
+
292
+ // ========== 更新 settings.json ==========
293
+ function updateSettings(hookPath) {
294
+ // 备份原始配置
295
+ backupSettings();
296
+
297
+ let settings = {};
298
+
299
+ if (fs.existsSync(settingsFile)) {
300
+ try {
301
+ const raw = fs.readFileSync(settingsFile, 'utf8');
302
+ settings = JSON.parse(raw);
303
+ console.log(`${GREEN}已读取现有配置: ${settingsFile}${NC}`);
304
+ } catch (err) {
305
+ console.log(`${RED}错误: settings.json 格式不正确 - ${err.message}${NC}`);
306
+ console.log(`${YELLOW}请手动检查: ${settingsFile}${NC}`);
307
+ console.log(`${YELLOW}备份已保存为: ${settingsFile}.bak${NC}`);
308
+ return false;
309
+ }
310
+ }
311
+
312
+ if (!settings.hooks) settings.hooks = {};
313
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
314
+
315
+ // 使用正斜杠路径(Claude Code 内部统一使用正斜杠)
316
+ const normalizedPath = hookPath.replace(/\\/g, '/');
317
+
318
+ // matcher 匹配所有常用工具 + MCP 工具
319
+ const matcher = 'Bash|Read|Write|Edit|Glob|Grep|mcp__*';
320
+
321
+ // 检查是否已存在同 matcher 的配置
322
+ const existingIdx = settings.hooks.PostToolUse.findIndex(h => h.matcher === matcher);
323
+
324
+ // hook command:直接用路径,不加 bash 前缀
325
+ // Claude Code 的 hook 执行器会自动通过 cr8() 找到 bash 并包装执行
326
+ const hookEntry = {
327
+ type: 'command',
328
+ command: normalizedPath,
329
+ };
330
+
331
+ const hookConfig = {
332
+ matcher: matcher,
333
+ hooks: [hookEntry]
334
+ };
335
+
336
+ if (existingIdx >= 0) {
337
+ settings.hooks.PostToolUse[existingIdx] = hookConfig;
338
+ console.log(`${GREEN}已更新: PostToolUse 钩子配置${NC}`);
339
+ } else {
340
+ settings.hooks.PostToolUse.push(hookConfig);
341
+ console.log(`${GREEN}已添加: PostToolUse 钩子配置${NC}`);
342
+ }
343
+
344
+ console.log(`${CYAN}钩子命令: ${normalizedPath}${NC}`);
345
+ console.log(`${CYAN}匹配模式: ${matcher}${NC}`);
346
+
347
+ try {
348
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
349
+ console.log(`${GREEN}已保存: ${settingsFile}${NC}`);
350
+ return true;
351
+ } catch (err) {
352
+ console.log(`${RED}写入失败: ${err.message}${NC}`);
353
+ console.log(`${YELLOW}请手动添加以下配置到 settings.json:${NC}`);
354
+ console.log(JSON.stringify({ hooks: { PostToolUse: [hookConfig] } }, null, 2));
355
+ return false;
356
+ }
357
+ }
358
+
359
+ // ========== 安装汉化 ==========
360
+ function installLocalize() {
361
+ console.log(`\n${MAGENTA}[2/2] 安装界面汉化...${NC}\n`);
362
+
363
+ ensureDir(localizeDir);
364
+
365
+ // 核心文件
366
+ const files = ['keyword.js', 'localize.js'];
367
+
368
+ files.forEach(file => {
369
+ const src = path.join(npmDir, 'localize', file);
370
+ const dest = path.join(localizeDir, file);
371
+ if (fs.existsSync(src)) {
372
+ copyFile(src, dest);
373
+ } else {
374
+ console.log(`${YELLOW}缺少文件: ${file}${NC}`);
375
+ }
376
+ });
377
+
378
+ // 执行汉化
379
+ console.log(`\n${MAGENTA}执行汉化...${NC}`);
380
+
381
+ try {
382
+ const jsScript = path.join(localizeDir, 'localize.js');
383
+ if (fs.existsSync(jsScript)) {
384
+ execSync(`node "${jsScript}"`, { stdio: 'inherit' });
385
+ }
386
+ } catch (err) {
387
+ console.log(`${YELLOW}警告: 汉化过程中遇到问题${NC}`);
388
+ console.log(`${YELLOW}可手动执行: node ~/.claude/localize/localize.js${NC}`);
389
+ }
390
+ }
391
+
392
+ // ========== 修复 /buddy 时间限制 ==========
393
+ function fixBuddyTimeLock(speciesIndex = null) {
394
+ console.log(`\n${MAGENTA}[3/3] 修复 /buddy 时间限制...${NC}\n`);
395
+
396
+ const availablePets = [
397
+ 'groom', 'cat', 'dog', 'wolf', 'fox', 'otter', 'bear', 'bunny',
398
+ 'raccoon', 'duck', 'owl', 'cow', 'capybara', 'cactus', 'robot',
399
+ 'rabbit', 'mushroom', 'chonk'
400
+ ];
401
+
402
+ const availablePetsCN = [
403
+ '理发师', '猫咪', '小狗', '小狼', '狐狸', '水獭', '小熊', '小兔',
404
+ '浣熊', '小鸭', '猫头鹰', '小牛', '水豚', '仙人掌', '机器人', '家兔',
405
+ '蘑菇', '胖猫'
406
+ ];
407
+
408
+ const availablePetsEmoji = [
409
+ '💇', '🐱', '🐕', '🐺', '🦊', '🦦', '🐻', '🐰', '🦝', '🦆', '🦉', '🐮',
410
+ '🦫', '🌵', '🤖', '🐇', '🍄', '😸'
411
+ ];
412
+
413
+ const availablePetsDisplay = [
414
+ '理发师 💇 (groom)', '猫咪 🐱 (cat)', '小狗 🐕 (dog)', '小狼 🐺 (wolf)',
415
+ '狐狸 🦊 (fox)', '水獭 🦦 (otter)', '小熊 🐻 (bear)', '小兔 🐰 (bunny)',
416
+ '浣熊 🦝 (raccoon)', '小鸭 🦆 (duck)', '猫头鹰 🦉 (owl)', '小牛 🐮 (cow)',
417
+ '水豚 🦫 (capybara)', '仙人掌 🌵 (cactus)', '机器人 🤖 (robot)',
418
+ '家兔 🐇 (rabbit)', '蘑菇 🍄 (mushroom)', '胖猫 😸 (chonk)'
419
+ ];
420
+
421
+ if (speciesIndex === null) {
422
+ speciesIndex = Math.floor(Math.random() * availablePets.length);
423
+ console.log(`${CYAN}随机选择宠物 [${speciesIndex + 1}]: ${availablePetsDisplay[speciesIndex]}${NC}`);
424
+ } else {
425
+ console.log(`${CYAN}选择宠物 [${speciesIndex + 1}]: ${availablePetsDisplay[speciesIndex]}${NC}`);
426
+ }
427
+
428
+ // 查找 Claude Code npm 包路径
429
+ let cliPath;
430
+ try {
431
+ const claudePkgPath = require.resolve('@anthropic-ai/claude-code/package.json');
432
+ cliPath = path.join(path.dirname(claudePkgPath), 'cli.js');
433
+ } catch (err) {
434
+ const homedir = require('os').homedir();
435
+ const knownPath = path.join(homedir, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
436
+ if (fs.existsSync(knownPath)) {
437
+ cliPath = knownPath;
438
+ } else {
439
+ console.log(`${YELLOW}警告: 找不到 Claude Code npm 包,跳过 /buddy 修复${NC}`);
440
+ return false;
441
+ }
442
+ }
443
+
444
+ if (!fs.existsSync(cliPath)) {
445
+ console.log(`${YELLOW}警告: cli.js 不存在,跳过 /buddy 修复${NC}`);
446
+ return false;
447
+ }
448
+
449
+ // 创建备份
450
+ const backupPath = cliPath + '.buddy-bak';
451
+ if (!fs.existsSync(backupPath)) {
452
+ fs.copyFileSync(cliPath, backupPath);
453
+ console.log(`${GREEN}已备份 cli.js 到 buddy-bak${NC}`);
454
+ }
455
+
456
+ try {
457
+ let content = fs.readFileSync(cliPath, 'utf8');
458
+
459
+ // 新版本 Zc8 函数 (Claude Code 2.1.91新版本)
460
+ const zc8Original = 'function Zc8(){if(Dq()!=="firstParty")return!1;if(XY())return!1;let q=new Date;return q.getFullYear()>2026||q.getFullYear()===2026&&q.getMonth()>=3}';
461
+ // 旧版本 Fd8 函数 (Claude Code 2.1.90旧版本)
462
+ const fd8Original = 'function Fd8(){if(Pq()!=="firstParty")return!1;if(XY())return!1;let q=new Date;return q.getFullYear()>2026||q.getFullYear()===2026&&q.getMonth()>=3}';
463
+
464
+ // 检查是否已修复
465
+ if (content.includes('function Zc8(){return!0}') || content.includes('function Fd8(){return!0}')) {
466
+ } else if (content.includes(zc8Original)) {
467
+ content = content.replace(zc8Original, 'function Zc8(){return!0}');
468
+ console.log(`${GREEN}/buddy 时间限制修复成功(新版本 Zc8)!${NC}`);
469
+ } else if (content.includes(fd8Original)) {
470
+ content = content.replace(fd8Original, 'function Fd8(){return!0}');
471
+ console.log(`${GREEN}/buddy 时间限制修复成功(旧版本 Fd8)!${NC}`);
472
+ } else {
473
+ console.log(`${YELLOW}警告: 未找到预期的 buddy 函数,版本可能不同${NC}`);
474
+ }
475
+
476
+ fs.writeFileSync(cliPath, content, 'utf8');
477
+
478
+ // 满级 buddy 补丁(legendary + 全属性100 + crown + shiny + penguin)
479
+ try {
480
+ let patchedContent = fs.readFileSync(cliPath, 'utf8');
481
+
482
+ // 新版本 hN_ 函数
483
+ const hnOriginal = 'function hN_(q){let K=NN_(q);return{bones:{rarity:K,species:QT6(q,DK4),eye:QT6(q,fK4),hat:K==="common"?"none":QT6(q,ZK4),shiny:q()<0.01,stats:EN_(q,K)},inspirationSeed:Math.floor(q()*1e9)}}';
484
+ const patchedHn = 'function hN_(q){let K=WK4[4];return{bones:{rarity:K,species:qT8,eye:QT6(q,fK4),hat:"crown",shiny:true,stats:{DEBUGGING:100,PATIENCE:100,CHAOS:100,WISDOM:100,SNARK:100}},inspirationSeed:999999999}}';
485
+
486
+ // 旧版本 yV_ 函数
487
+ const yvOriginal = 'function yV_(q){let K=TV_(q);return{bones:{rarity:K,species:yT6(q,S44),eye:yT6(q,C44),hat:K==="common"?"none":yT6(q,b44),shiny:q()<0.01,stats:VV_(q,K)},inspirationSeed:Math.floor(q()*1e9)}}';
488
+ const patchedYv = 'function yV_(q){let K="legendary";return{bones:{rarity:K,species:yT6(q,S44),eye:yT6(q,C44),hat:"crown",shiny:true,stats:{DEBUGGING:100,PATIENCE:100,CHAOS:100,WISDOM:100,SNARK:100}},inspirationSeed:999999999}}';
489
+
490
+ if (patchedContent.includes(hnOriginal)) {
491
+ patchedContent = patchedContent.replace(hnOriginal, patchedHn);
492
+ console.log(`${GREEN}buddy 满级补丁已应用(新版本 hN_)${NC}`);
493
+ } else if (patchedContent.includes(yvOriginal)) {
494
+ patchedContent = patchedContent.replace(yvOriginal, patchedYv);
495
+ console.log(`${GREEN}buddy 满级补丁已应用(旧版本 yV_)${NC}`);
496
+ } else if (patchedContent.includes('WK4[4]') && patchedContent.includes('qT8') && patchedContent.includes('"crown"')) {
497
+ console.log(`${YELLOW}buddy 满级[已激活]${NC}`);
498
+ } else if (patchedContent.includes('rarity:"legendary"')) {
499
+ console.log(`${GREEN}buddy 满级[已激活]${NC}`);
500
+ } else {
501
+ console.log(`${YELLOW}buddy 生成函数未匹配,跳过${NC}`);
502
+ }
503
+
504
+ // 修复显示函数 - 新版本 Gc8
505
+ const gc8Original = 'function Gc8(q,K=0){let _=hUK[q.species],Y=[..._[K%_.length].map(($)=>$.replaceAll("{E}",q.eye))];if(q.hat!=="none"&&!Y[0].trim())Y[0]=XbY[q.hat];if(!Y[0].trim()&&_.every(($)=>!$[0].trim()))Y.shift();return Y}';
506
+ const patchedGc8 = 'function Gc8(q,K=0){if(!hUK)pz7();let _=hUK[q.species]||hUK[qT8];if(!_)return["no buddy graphic"];let Y=[..._[K%_.length].map(($)=>$.replaceAll("{E}",q.eye))];if(q.hat!=="none"&&!Y[0].trim())Y[0]=XbY[q.hat];if(!Y[0].trim()&&_.every(($)=>!$[0].trim()))Y.shift();return Y}';
507
+
508
+ // 修复显示函数 - 旧版本 Ud8
509
+ const ud8Original = 'function Ud8(q,K=0){let _=RFK[q.species],Y=[..._[K%_.length].map(($)=>$.replaceAll("{E}",q.eye))];if(q.hat!=="none"&&!Y[0].trim())Y[0]=XCY[q.hat];if(!Y[0].trim()&&_.every(($)=>!$[0].trim()))Y.shift();return Y}';
510
+ const patchedUd8 = 'function Ud8(q,K=0){if(!RFK)Oz7();let _=RFK[q.species]||RFK[Tv8];if(!_)return["no buddy graphic"];let Y=[..._[K%_.length].map(($)=>$.replaceAll("{E}",q.eye))];if(q.hat!=="none"&&!Y[0].trim())Y[0]=XCY[q.hat];if(!Y[0].trim()&&_.every(($)=>!$[0].trim()))Y.shift();return Y}';
511
+
512
+ if (patchedContent.includes(gc8Original)) {
513
+ patchedContent = patchedContent.replace(gc8Original, patchedGc8);
514
+ console.log(`${GREEN}显示函数已修复(新版本 Gc8)${NC}`);
515
+ } else if (patchedContent.includes(ud8Original)) {
516
+ patchedContent = patchedContent.replace(ud8Original, patchedUd8);
517
+ console.log(`${GREEN}显示函数已修复(旧版本 Ud8)${NC}`);
518
+ } else if (patchedContent.includes('if(!hUK)pz7()') || patchedContent.includes('if(!RFK)Oz7()')) {
519
+ } else {
520
+ console.log(`${YELLOW}显示函数未匹配,跳过${NC}`);
521
+ }
522
+
523
+ // 修复 SHINY 翻译
524
+ if (patchedContent.includes('"✨ 闪光 ✨"')) {
525
+ patchedContent = patchedContent.replace('"✨ 闪光 ✨"', '"✨ SHINY ✨"');
526
+ console.log(`${GREEN}SHINY 翻译已修复${NC}`);
527
+ }
528
+
529
+ fs.writeFileSync(cliPath, patchedContent, 'utf8');
530
+ console.log(`${GREEN}\n修复完成!请重启 Claude Code${NC}`);
531
+ } catch (err) {
532
+ console.log(`${YELLOW}满级补丁应用失败: ${err.message}${NC}`);
533
+ }
534
+
535
+ return true;
536
+ } catch (err) {
537
+ console.log(`${RED}修复失败: ${err.message}${NC}`);
538
+ console.log(`${YELLOW}可能需要管理员权限${NC}`);
539
+ return false;
540
+ }
541
+ }
542
+
543
+ // ========== 诊断模式 ==========
544
+ function runDiagnostics() {
545
+ console.log(`${MAGENTA}==============================================${NC}`);
546
+ console.log(`${MAGENTA} Polished Localization for Claude Code 诊断模式${NC}`);
547
+ console.log(`${MAGENTA}==============================================${NC}\n`);
548
+
549
+ let allOk = true;
550
+
551
+ // 1. 检查 hook 脚本文件
552
+ console.log(`${CYAN}[1/5] 检查钩子脚本文件...${NC}`);
553
+ const hookFile = path.join(hooksDir, 'tool-tips-post.sh');
554
+ if (fs.existsSync(hookFile)) {
555
+ console.log(`${GREEN} 文件存在: ${hookFile}${NC}`);
556
+
557
+ // 检查换行符
558
+ const buf = fs.readFileSync(hookFile);
559
+ if (buf.includes(Buffer.from('\r\n'))) {
560
+ console.log(`${RED} 问题: 文件包含 CRLF 换行符! bash 无法正确执行${NC}`);
561
+ console.log(`${YELLOW} 修复: 运行 polished-localization-install 自动修复${NC}`);
562
+ allOk = false;
563
+ } else {
564
+ console.log(`${GREEN} 换行符: LF (正确)${NC}`);
565
+ }
566
+
567
+ // 检查文件大小
568
+ const stat = fs.statSync(hookFile);
569
+ if (stat.size < 100) {
570
+ console.log(`${RED} 问题: 文件过小 (${stat.size} bytes),可能不完整${NC}`);
571
+ allOk = false;
572
+ } else {
573
+ console.log(`${GREEN} 文件大小: ${stat.size} bytes${NC}`);
574
+ }
575
+
576
+ // 检查 shebang
577
+ const firstLine = fs.readFileSync(hookFile, 'utf8').split('\n')[0];
578
+ if (firstLine.startsWith('#!/bin/bash') || firstLine.startsWith('#!/usr/bin/env bash')) {
579
+ console.log(`${GREEN} shebang: ${firstLine}${NC}`);
580
+ } else {
581
+ console.log(`${YELLOW} 警告: 第一行不是标准 shebang: ${firstLine}${NC}`);
582
+ }
583
+ } else {
584
+ console.log(`${RED} 问题: 钩子脚本不存在!${NC}`);
585
+ console.log(`${YELLOW} 修复: 运行 polished-localization-install${NC}`);
586
+ allOk = false;
587
+ }
588
+
589
+ // 2. 检查 settings.json
590
+ console.log(`\n${CYAN}[2/5] 检查 settings.json...${NC}`);
591
+ if (fs.existsSync(settingsFile)) {
592
+ console.log(`${GREEN} 文件存在: ${settingsFile}${NC}`);
593
+ try {
594
+ const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
595
+
596
+ if (settings.hooks && settings.hooks.PostToolUse) {
597
+ const hookConfig = settings.hooks.PostToolUse.find(h =>
598
+ h.matcher && h.matcher.includes('Bash')
599
+ );
600
+ if (hookConfig) {
601
+ console.log(`${GREEN} 钩子配置已找到${NC}`);
602
+ console.log(`${GREEN} matcher: ${hookConfig.matcher}${NC}`);
603
+ if (hookConfig.hooks && hookConfig.hooks[0]) {
604
+ console.log(`${GREEN} command: ${hookConfig.hooks[0].command}${NC}`);
605
+
606
+ // 验证命令路径指向的文件是否存在
607
+ const cmdPath = hookConfig.hooks[0].command.replace(/\//g, path.sep);
608
+ if (fs.existsSync(cmdPath)) {
609
+ console.log(`${GREEN} 命令路径有效${NC}`);
610
+ } else {
611
+ console.log(`${RED} 问题: 命令路径指向的文件不存在: ${cmdPath}${NC}`);
612
+ allOk = false;
613
+ }
614
+ }
615
+ } else {
616
+ console.log(`${RED} 问题: 未找到包含 Bash 的 matcher 配置${NC}`);
617
+ allOk = false;
618
+ }
619
+ } else {
620
+ console.log(`${RED} 问题: settings.json 中没有 hooks.PostToolUse 配置${NC}`);
621
+ allOk = false;
622
+ }
623
+ } catch (err) {
624
+ console.log(`${RED} 问题: settings.json 格式不正确 - ${err.message}${NC}`);
625
+ allOk = false;
626
+ }
627
+ } else {
628
+ console.log(`${RED} 问题: settings.json 不存在${NC}`);
629
+ allOk = false;
630
+ }
631
+
632
+ // 3. 检查 bash 可用性
633
+ console.log(`\n${CYAN}[3/5] 检查 bash 环境...${NC}`);
634
+ if (IS_WIN) {
635
+ const bashPath = findGitBash();
636
+ if (bashPath) {
637
+ console.log(`${GREEN} Git Bash: ${bashPath}${NC}`);
638
+ } else {
639
+ console.log(`${RED} 问题: 未找到 Git Bash${NC}`);
640
+ console.log(`${YELLOW} 修复: 安装 Git for Windows (https://git-scm.com/downloads/win)${NC}`);
641
+ allOk = false;
642
+ }
643
+ } else {
644
+ try {
645
+ const bashVer = execSync('bash --version', { encoding: 'utf8' }).split('\n')[0];
646
+ console.log(`${GREEN} ${bashVer}${NC}`);
647
+ } catch (err) {
648
+ console.log(`${RED} 问题: bash 不可用${NC}`);
649
+ allOk = false;
650
+ }
651
+ }
652
+
653
+ // 4. 运行 hook 验证
654
+ console.log(`\n${CYAN}[4/5] 运行钩子验证测试...${NC}`);
655
+ if (fs.existsSync(hookFile)) {
656
+ const verify = verifyHook(hookFile);
657
+ if (verify.ok) {
658
+ console.log(`${GREEN} 验证通过!${NC}`);
659
+ console.log(`${GREEN} 输出: ${verify.output.substring(0, 120)}${NC}`);
660
+ } else {
661
+ console.log(`${RED} 问题: ${verify.error}${NC}`);
662
+ allOk = false;
663
+ }
664
+ } else {
665
+ console.log(`${YELLOW} 跳过: 钩子文件不存在${NC}`);
666
+ allOk = false;
667
+ }
668
+
669
+ // 5. Windows 特殊检查
670
+ console.log(`\n${CYAN}[5/5] 系统环境检查...${NC}`);
671
+ if (IS_WIN) {
672
+ try {
673
+ const autocrlf = execSync('git config --global core.autocrlf', { encoding: 'utf8' }).trim();
674
+ if (autocrlf === 'input') {
675
+ console.log(`${GREEN} core.autocrlf = input (正确)${NC}`);
676
+ } else {
677
+ console.log(`${RED} 问题: core.autocrlf = ${autocrlf || '(未设置)'}${NC}`);
678
+ console.log(`${YELLOW} 修复: git config --global core.autocrlf input${NC}`);
679
+ console.log(`${YELLOW} 原因: true 会在 checkout 时把 LF 转为 CRLF,导致 .sh 脚本损坏${NC}`);
680
+ allOk = false;
681
+ }
682
+ } catch (err) {
683
+ console.log(`${YELLOW} 警告: 无法检查 git config${NC}`);
684
+ }
685
+
686
+ // 检查路径是否含中文
687
+ if (homeDir.match(/[\u4e00-\u9fff]/)) {
688
+ console.log(`${YELLOW} 注意: 用户路径含中文字符: ${homeDir}${NC}`);
689
+ console.log(`${YELLOW} 通常没问题,但某些旧工具可能报错${NC}`);
690
+ }
691
+ }
692
+
693
+ // 总结
694
+ console.log(`\n${MAGENTA}${'='.repeat(50)}${NC}`);
695
+ if (allOk) {
696
+ console.log(`${GREEN} 所有检查通过! 钩子应该正常工作${NC}`);
697
+ console.log(`${YELLOW} 如果仍然不显示提示,请重启 Claude Code${NC}`);
698
+ } else {
699
+ console.log(`${RED} 发现问题! 请根据上方提示修复${NC}`);
700
+ console.log(`${YELLOW} 修复后运行: polished-localization-install --verify${NC}`);
701
+ }
702
+ console.log(`${MAGENTA}${'='.repeat(50)}${NC}\n`);
703
+
704
+ return allOk;
705
+ }
706
+
707
+ // ========== 主流程 ==========
708
+ function main() {
709
+ const args = process.argv.slice(2);
710
+
711
+ // 诊断模式
712
+ if (args.includes('--verify') || args.includes('-v') || args.includes('verify')) {
713
+ runDiagnostics();
714
+ return;
715
+ }
716
+
717
+ // 解析宠物选择参数
718
+ let speciesIndex = null;
719
+ const buddyArg = args.find(arg => arg.startsWith('--buddy='));
720
+ if (buddyArg) {
721
+ const index = parseInt(buddyArg.split('=')[1], 10);
722
+ if (!isNaN(index) && index >= 1 && index <= 18) {
723
+ speciesIndex = index - 1; // 转换为数组索引(从0开始)
724
+ } else {
725
+ console.log(`${RED}无效的宠物索引: ${index} (有效范围: 1-18)${NC}`);
726
+ console.log(`${CYAN}可用宠物列表:${NC}`);
727
+ const petsDisplay = [
728
+ '理发师 💇 (groom)', '猫咪 🐱 (cat)', '小狗 🐕 (dog)', '小狼 🐺 (wolf)',
729
+ '狐狸 🦊 (fox)', '水獭 🦦 (otter)', '小熊 🐻 (bear)', '小兔 🐰 (bunny)',
730
+ '浣熊 🦝 (raccoon)', '小鸭 🦆 (duck)', '猫头鹰 🦉 (owl)', '小牛 🐮 (cow)',
731
+ '水豚 🦫 (capybara)', '仙人掌 🌵 (cactus)', '机器人 🤖 (robot)',
732
+ '家兔 🐇 (rabbit)', '蘑菇 🍄 (mushroom)', '胖猫 😸 (chonk)'
733
+ ];
734
+ petsDisplay.forEach((pet, i) => console.log(` ${i + 1}: ${pet}`));
735
+ return;
736
+ }
737
+ }
738
+
739
+ // 单独修复 /buddy 模式
740
+ if (args.includes('--fix-buddy')) {
741
+ const buddyOk = fixBuddyTimeLock(speciesIndex);
742
+ if (buddyOk) {
743
+ console.log(`\n${GREEN}修复完成!请重启 Claude Code${NC}\n`);
744
+ }
745
+ return;
746
+ }
747
+
748
+ const mode = args[0] || 'all';
749
+ let hookOk = null;
750
+ let locOk = null;
751
+ let buddyOk = null;
752
+
753
+ switch (mode) {
754
+ case 'hook':
755
+ case '1':
756
+ hookOk = installHook();
757
+ locOk = false;
758
+ buddyOk = null;
759
+ break;
760
+ case 'localize':
761
+ case '2':
762
+ installLocalize();
763
+ hookOk = null;
764
+ locOk = true;
765
+ buddyOk = null;
766
+ break;
767
+ case 'all':
768
+ default:
769
+ hookOk = installHook();
770
+ installLocalize();
771
+ locOk = true;
772
+ buddyOk = fixBuddyTimeLock(speciesIndex);
773
+ break;
774
+ }
775
+
776
+ // 最终总结
777
+ console.log(`\n${MAGENTA}${'='.repeat(50)}${NC}`);
778
+ console.log(`${MAGENTA} 安装完成!${NC}`);
779
+ console.log(`${MAGENTA}${'='.repeat(50)}${NC}`);
780
+
781
+ if (hookOk === true) {
782
+ console.log(`${GREEN} 钩子: 已安装${NC}`);
783
+ } else if (hookOk === false) {
784
+ console.log(`${RED} 钩子: 安装失败或有问题${NC}`);
785
+ if (IS_WIN) {
786
+ console.log(`${YELLOW} 运行诊断: polished-localization-install --verify${NC}`);
787
+ }
788
+ } else {
789
+ console.log(`${YELLOW} 钩子: 未执行(当前模式仅安装汉化)${NC}`);
790
+ }
791
+
792
+ if (locOk === true) {
793
+ console.log(`${GREEN} 汉化: 已安装${NC}`);
794
+ } else if (locOk === false) {
795
+ console.log(`${YELLOW} 汉化: 未执行${NC}`);
796
+ }
797
+
798
+ if (buddyOk === true) {
799
+ console.log(`${GREEN} /buddy: 已修复时间限制${NC}`);
800
+ } else if (buddyOk === false) {
801
+ console.log(`${YELLOW} /buddy: 修复失败(可能需要管理员权限)${NC}`);
802
+ }
803
+
804
+ console.log(`${YELLOW} 请重启 Claude Code 使所有更改生效${NC}`);
805
+
806
+ if (IS_WIN) {
807
+ console.log(`${CYAN} 如遇问题运行: polished-localization-install --verify${NC}`);
808
+ }
809
+
810
+ console.log(`${CYAN} 项目主页: ${pkg.homepage}${NC}\n`);
811
+ }
812
+
813
+ main();