-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
603 lines (553 loc) · 22.8 KB
/
Copy pathserver.js
File metadata and controls
603 lines (553 loc) · 22.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
#!/usr/bin/env node
// mcode CLI i18n MCP server
// Tools: install / uninstall / switch / status
// Auto-installs on first run (silently skips if mcode not detected).
import {
readFileSync, writeFileSync, existsSync, mkdirSync,
copyFileSync, readdirSync, unlinkSync, rmSync,
} from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir, platform } from "node:os";
import { execSync } from "node:child_process";
// ─── Paths ─────────────────────────────────────────────────────────────
const HERE = dirname(fileURLToPath(import.meta.url));
const BUNDLED_SHIM = join(HERE, "i18n-shim.mjs");
const BUNDLED_PACKS = join(HERE, "i18n-packs");
const BUNDLED_PS1 = join(HERE, "mcode.ps1");
const HOME = homedir();
const MCODE_HOME = join(HOME, ".mcode");
const CONFIG_PATH = join(MCODE_HOME, "config.json");
const LOG_PATH = join(MCODE_HOME, "logs", "mcode-cli-zh.log");
const MCODE_DIR_CACHE = join(MCODE_HOME, ".i18n-mcode-dir"); // 记住 mcode 位置
const PLUGIN_VERSION = "0.6.0";
// 读取/保存 mcode 位置(下次自动用)
function getCachedMcodeDir() {
try {
if (existsSync(MCODE_DIR_CACHE)) {
const dir = readFileSync(MCODE_DIR_CACHE, "utf8").trim();
if (dir && existsSync(join(dir, "mcode.cmd"))) return dir;
}
} catch {}
return null;
}
function saveCachedMcodeDir(dir) {
try {
mkdirSync(MCODE_HOME, { recursive: true });
writeFileSync(MCODE_DIR_CACHE, dir, "utf8");
log("cached mcodeDir:", dir);
} catch (e) {
log("failed to cache mcodeDir:", e.message);
}
}
// ─── Logging ───────────────────────────────────────────────────────────
function log(...args) {
try {
mkdirSync(dirname(LOG_PATH), { recursive: true });
const line = `${new Date().toISOString()} ${args.map(a => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}\n`;
writeFileSync(LOG_PATH, line, { flag: "a" });
} catch { /* swallow */ }
}
// ─── Detect mcode install ─────────────────────────────────────────────
function detectMcodeDir() {
// 0) 上次记住的路径(优先,免探测)
const cached = getCachedMcodeDir();
if (cached) return cached;
// 1) `where mcode` (Windows) or `which mcode` (Unix)
try {
const cmd = platform === "win32" ? "where.exe mcode" : "which mcode";
const out = execSync(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
const first = out.split(/\r?\n/).map(s => s.trim()).find(s => s && (s.endsWith(".cmd") || s.endsWith(".ps1") || !s.includes(" ")));
if (first && existsSync(first)) return dirname(first);
} catch { /* ignore */ }
// 2) npm global root
try {
const out = execSync("npm root -g", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
const globalRoot = out.trim();
const cliJs = join(globalRoot, "@minimax-ai", "code", "cli.js");
if (existsSync(cliJs)) return dirname(globalRoot);
} catch { /* ignore */ }
// 3) 常见自定义位置
const customPaths = [
join(HOME, ".minimax-code"),
join(HOME, "mcode"),
join(HOME, ".mcode", "mcode"),
];
for (const p of customPaths) {
if (existsSync(join(p, "mcode.cmd"))) return p;
}
// 4) MCODE_DIR 环境变量
if (process.env.MCODE_DIR && existsSync(join(process.env.MCODE_DIR, "mcode.cmd"))) {
return process.env.MCODE_DIR;
}
return null;
}
// 帮助信息:探测失败时给用户提示
function mcodeNotFoundHint() {
return "mcode installation not found. Try one of:\n" +
" 1) Pass mcodeDir explicitly: mcode_i18n_install(mcodeDir='<path>')\n" +
" 2) Add mcode to PATH and restart Mavis\n" +
" 3) Verify mcode is installed: where mcode (Windows) or which mcode (Unix)";
}
// ─── Helpers ───────────────────────────────────────────────────────────
function copyDirRecursive(src, dst) {
mkdirSync(dst, { recursive: true });
for (const entry of readdirSync(src, { withFileTypes: true })) {
const s = join(src, entry.name);
const d = join(dst, entry.name);
if (entry.isDirectory()) copyDirRecursive(s, d);
else if (entry.isFile()) copyFileSync(s, d);
}
}
function readJson(path, fallback) {
try { return JSON.parse(readFileSync(path, "utf8")); }
catch { return fallback; }
}
function writeJson(path, obj) {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
}
// ─── 鲁棒 shim 注入:不依赖具体启动器模板格式 ────────────────────────────
// 找含 cli.js 的命令行引用,提取路径前缀,在 cli.js 前面插 --import + shim 参数
// 覆盖:
// - mcode.cmd (Windows): "%dp0%\..." 或 "%~dp0\..." 风格
// - mcode (POSIX shell): "$basedir/..." 风格
// - mcode.ps1 (PowerShell): $basedir/... 风格(已含 $i18nShim 变量)
// 返回 { content, injected, reason }
function injectShimIntoLauncher(content, mcodeDir) {
// 1. Windows 风格: "%dp0%\node_modules\...\cli.js" 或 "%~dp0\...\cli.js"
const winRe = /"(%[~]?dp0%?[^"]*?cli\.js)"/;
const winMatch = content.match(winRe);
if (winMatch) {
const cliPath = winMatch[1];
// 提取前缀变量: %dp0% 或 %~dp0
const dpVar = cliPath.match(/^%[~]?dp0%?/)[0];
// 构造 shim file URL: file:///%~dp0\i18n-shim.mjs
// 注意:file:/// + dpVar + \i18n-shim.mjs,但 dpVar 含 %,需要裸路径段
// 简化:把 dpVar 的 % 去掉,得到 ~dp0 或 dp0,然后拼 file:///~dp0\i18n-shim.mjs
// 实际上 mcode.cmd 用 "%~dp0" 时,file:// 直接拼 %~dp0 是不行的(URL 不合法)
// 但 cmd 的 % 是变量引用,真实路径是变量值,需要在 file:// 后面跟的是字面路径
// 标准做法:file:///%~dp0\i18n-shim.mjs → cmd 展开后 file:///C:\Users\...\i18n-shim.mjs
// 所以 file:// 后面跟 dpVar(不变)即可
const shimArg = `"--import" "file:///${dpVar}\\i18n-shim.mjs"`;
const newContent = content.replace(
`"${cliPath}"`,
`${shimArg} "${cliPath}"`
);
return { content: newContent, injected: true, reason: "windows style" };
}
// 2. PS1 风格: 内容已含 $i18nShim 变量,只需在 cli.js 引用前插 --import "$i18nShim"
// 模板行: & "$basedir/node$exe" "$cliEntry" $args
// 改成: & "$basedir/node$exe" --import "$i18nShim" "$cliEntry" $args
if (content.includes("$i18nShim") || content.includes("i18nShimPath")) {
const ps1Re = /"(\$\{?basedir\}?\/[^"'\s]*cli\.js)"/g;
if (ps1Re.test(content)) {
const newContent = content.replace(
ps1Re,
'--import "$i18nShim" "$1"'
);
return { content: newContent, injected: true, reason: "ps1 style (--import shim 注入)" };
}
}
// 3. POSIX shell 风格: "$basedir/.../cli.js"
// 模板: exec "$basedir/node" "$basedir/node_modules/@minimax-ai/code/cli.js" "$@"
// 改成: exec "$basedir/node" --import "file:///$basedir/../i18n-shim.mjs" "$basedir/.../cli.js" "$@"
const shRe = /"(\$\{?basedir\}?\/[^"'\s]*cli\.js)"/;
const shMatch = content.match(shRe);
if (shMatch) {
if (content.includes("i18n_shim_url")) {
return { content, injected: false, reason: "posix shell already has shim wrapper" };
}
// 插入 wrapper 段:cygpath 转换 + shim URL 变量
// 找第一个 "if [ -x \"$basedir/node\" ]; then" 行,前面插 wrapper
const wrapper = `# i18n-shim injection (mcode-cli-zh Plugin)\n` +
`case \`uname\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir=\`cygpath -w "$basedir"\`; fi ;; esac\n` +
`i18n_shim_url="file:///\${basedir//\\\\/\\/}/i18n-shim.mjs"\n`;
// 找 exec 行前插
const newContent = content.replace(
/(\nif \[ -x "\$basedir\/node" \]; then)/,
`\n${wrapper}$1`
);
if (newContent === content) {
// 备用:在第一个 exec 之前插
const altNew = content.replace(
/(\nexec )/,
`\n${wrapper}$1`
);
if (altNew !== content) {
return { content: altNew, injected: true, reason: "posix shell style (fallback)" };
}
return { content, injected: false, reason: "posix shell: no anchor for wrapper" };
}
return { content: newContent, injected: true, reason: "posix shell style" };
}
return { content, injected: false, reason: "no cli.js path pattern matched" };
}
// ─── Tool: install ────────────────────────────────────────────────────
function install(args) {
// 优先用参数指定的 mcodeDir,fallback 到探测
let mcodeDir = args?.mcodeDir;
if (!mcodeDir) {
mcodeDir = detectMcodeDir();
} else {
log("install: using user-provided mcodeDir:", mcodeDir);
}
if (!mcodeDir) throw new Error(mcodeNotFoundHint());
const steps = [];
const cmdPath = join(mcodeDir, "mcode.cmd");
const shPath = join(mcodeDir, "mcode");
const ps1Path = join(mcodeDir, "mcode.ps1");
const shimPath = join(mcodeDir, "i18n-shim.mjs");
const packsPath = join(mcodeDir, "i18n-packs");
const cmdBakPath = join(mcodeDir, "mcode.cmd.bak");
const shBakPath = join(mcodeDir, "mcode.bak");
if (!existsSync(cmdPath)) throw new Error(`mcode.cmd not found at ${cmdPath}`);
// 1. Copy shim
copyFileSync(BUNDLED_SHIM, shimPath);
steps.push(`copied shim -> ${shimPath}`);
// 2. Copy packs
mkdirSync(packsPath, { recursive: true });
copyDirRecursive(BUNDLED_PACKS, packsPath);
steps.push(`copied packs -> ${packsPath}`);
// 3. Modify mcode.cmd (Windows cmd launcher)
// 鲁棒注入:不依赖具体模板格式,适配旧模板(%dp0%)和新模板(%~dp0 + 嵌入 node)
let cmdContent = readFileSync(cmdPath, "utf8");
if (cmdContent.includes("i18n-shim.mjs")) {
steps.push("mcode.cmd already has i18n-shim.mjs, skipping");
} else {
if (!existsSync(cmdBakPath)) {
copyFileSync(cmdPath, cmdBakPath);
steps.push(`backed up -> ${cmdBakPath}`);
} else {
steps.push("mcode.cmd.bak already exists, not overwriting");
}
const cmdResult = injectShimIntoLauncher(cmdContent, mcodeDir);
if (cmdResult.injected) {
writeFileSync(cmdPath, cmdResult.content, "utf8");
steps.push(`injected --import into mcode.cmd (${cmdResult.reason})`);
} else {
steps.push(`WARN: mcode.cmd format unexpected, --import not injected (${cmdResult.reason})`);
}
}
// 4. Modify mcode (POSIX shell launcher) - same shim injection
if (existsSync(shPath)) {
let shContent = readFileSync(shPath, "utf8");
if (shContent.includes("i18n-shim.mjs")) {
steps.push("mcode (shell) already has i18n-shim.mjs, skipping");
} else {
if (!existsSync(shBakPath)) {
copyFileSync(shPath, shBakPath);
steps.push(`backed up -> ${shBakPath}`);
} else {
steps.push("mcode.bak already exists, not overwriting");
}
const shResult = injectShimIntoLauncher(shContent, mcodeDir);
if (shResult.injected) {
writeFileSync(shPath, shResult.content, "utf8");
steps.push(`injected shim into mcode (shell) (${shResult.reason})`);
} else {
steps.push(`WARN: mcode (shell) format unexpected, shim not injected (${shResult.reason})`);
}
}
}
// 5. Create/restore mcode.ps1 (PowerShell launcher template)
// 注意:如果存在 mcode.ps1.disabled(用户主动禁用的),不要自动还原
// 但 mcode update 会把 mcode.ps1 简化成 205 字节(无 shim 注入),需要检测并修复
const ps1DisabledPath = join(mcodeDir, "mcode.ps1.disabled");
if (existsSync(ps1Path) && existsSync(BUNDLED_PS1)) {
const ps1Content = readFileSync(ps1Path, "utf8");
if (!ps1Content.includes("i18n-shim") && !ps1Content.includes("$i18nShim")) {
// mcode.ps1 存在但不含 shim 注入(可能是 mcode update 简化了) → 覆盖回 Plugin 模板
copyFileSync(BUNDLED_PS1, ps1Path);
steps.push(`mcode.ps1 缺少 shim 注入,已覆盖回 Plugin 模板`);
} else {
steps.push(`mcode.ps1 OK,跳过`);
}
} else if (!existsSync(ps1Path) && existsSync(ps1DisabledPath)) {
steps.push(`mcode.ps1.disabled exists, not auto-restoring (user disabled intentionally)`);
} else if (!existsSync(ps1Path) && existsSync(BUNDLED_PS1)) {
copyFileSync(BUNDLED_PS1, ps1Path);
steps.push(`created -> ${ps1Path}`);
}
// 6. Ensure user config exists
if (!existsSync(CONFIG_PATH)) {
writeJson(CONFIG_PATH, { language: "zh-CN", enabled: true });
steps.push(`created -> ${CONFIG_PATH}`);
} else {
// If config exists but missing enabled field, default to true
const cfg = readJson(CONFIG_PATH, {});
if (cfg.enabled === undefined) {
cfg.enabled = true;
writeJson(CONFIG_PATH, cfg);
steps.push(`added enabled=true to existing config`);
}
}
log("install:", steps.join(" | "));
// 记住 mcodeDir,下次自动用
saveCachedMcodeDir(mcodeDir);
return { success: true, mcodeDir, steps };
}
// ─── Tool: uninstall ──────────────────────────────────────────────────
function uninstall(args) {
let mcodeDir = args?.mcodeDir;
if (!mcodeDir) mcodeDir = detectMcodeDir();
if (!mcodeDir) return { success: true, message: "mcode not found, nothing to uninstall" };
const steps = [];
const cmdPath = join(mcodeDir, "mcode.cmd");
const shPath = join(mcodeDir, "mcode");
const ps1Path = join(mcodeDir, "mcode.ps1");
const shimPath = join(mcodeDir, "i18n-shim.mjs");
const packsPath = join(mcodeDir, "i18n-packs");
const cmdBakPath = join(mcodeDir, "mcode.cmd.bak");
const shBakPath = join(mcodeDir, "mcode.bak");
// 1. Restore mcode.cmd from .bak
if (existsSync(cmdBakPath)) {
copyFileSync(cmdBakPath, cmdPath);
unlinkSync(cmdBakPath);
steps.push("restored mcode.cmd from .bak, removed .bak");
} else if (existsSync(cmdPath)) {
const content = readFileSync(cmdPath, "utf8");
const cleaned = content.replace(/ --import "file:\/\/\/[^"]*" ?/g, " ");
if (cleaned !== content) {
writeFileSync(cmdPath, cleaned, "utf8");
steps.push("removed --import from mcode.cmd (no backup was available)");
}
}
// 2. Restore mcode (shell) from .bak
if (existsSync(shBakPath)) {
copyFileSync(shBakPath, shPath);
unlinkSync(shBakPath);
steps.push("restored mcode (shell) from .bak, removed .bak");
} else if (existsSync(shPath)) {
const content = readFileSync(shPath, "utf8");
const cleaned = content.replace(/\s*--import\s+["']file:\/\/\/[^"']*["']\s*/g, " ");
if (cleaned !== content) {
writeFileSync(shPath, cleaned, "utf8");
steps.push("removed --import from mcode (shell) (no backup was available)");
}
}
// 3. Remove shim
if (existsSync(shimPath)) { unlinkSync(shimPath); steps.push("removed i18n-shim.mjs"); }
// 4. Remove packs
if (existsSync(packsPath)) {
try { rmSync(packsPath, { recursive: true, force: true }); steps.push("removed i18n-packs/"); }
catch (e) { steps.push(`WARN: could not remove i18n-packs/: ${e.message}`); }
}
// 5. Remove mcode.ps1
if (existsSync(ps1Path)) {
try { unlinkSync(ps1Path); steps.push("removed mcode.ps1"); }
catch (e) { steps.push(`WARN: could not remove mcode.ps1: ${e.message}`); }
}
log("uninstall:", steps.join(" | "));
return { success: true, mcodeDir, steps };
}
// ─── Tool: switch ─────────────────────────────────────────────────────
function switchLang(args) {
const locale = args?.locale;
if (!locale || !["zh-CN", "zh-TW", "en"].includes(locale)) {
throw new Error("locale must be one of: zh-CN, zh-TW, en");
}
// switch 只改 ~/.mcode/config.json,不需要 mcode 安装检测
const config = readJson(CONFIG_PATH, {});
config.language = locale;
writeJson(CONFIG_PATH, config);
log("switch:", locale, "config:", CONFIG_PATH);
return {
success: true,
locale,
configPath: CONFIG_PATH,
note: "Restart mcode for the change to take effect.",
};
}
// ─── Tool: translate (on/off switch) ──────────────────────────────────
function setEnabled(args) {
const on = args?.on;
if (typeof on !== "boolean") {
throw new Error("on must be true or false");
}
// translate 只改 ~/.mcode/config.json,不需要 mcode 安装检测
const config = readJson(CONFIG_PATH, {});
config.enabled = on;
writeJson(CONFIG_PATH, config);
log("translate:", on ? "on" : "off", "config:", CONFIG_PATH);
return {
success: true,
enabled: on,
configPath: CONFIG_PATH,
note: "Restart mcode for the change to take effect.",
};
}
// ─── Tool: status ─────────────────────────────────────────────────────
function getStatus(args) {
// 也接受 mcodeDir 参数(给非标准位置用,跟 install 一样)
let mcodeDir = args?.mcodeDir;
if (!mcodeDir) mcodeDir = detectMcodeDir();
const s = {
mcodeInstalled: !!mcodeDir,
mcodeDir,
shimInstalled: false,
shimPath: null,
packsInstalled: false,
packsPath: null,
cmdModified: false,
cmdBackupExists: false,
ps1Installed: false,
translationEnabled: true,
userLanguage: null,
userConfig: null,
userConfigPath: CONFIG_PATH,
pluginVersion: PLUGIN_VERSION,
};
if (mcodeDir) {
const shimPath = join(mcodeDir, "i18n-shim.mjs");
const packsPath = join(mcodeDir, "i18n-packs");
const cmdPath = join(mcodeDir, "mcode.cmd");
const bakPath = join(mcodeDir, "mcode.cmd.bak");
const ps1Path = join(mcodeDir, "mcode.ps1");
s.shimInstalled = existsSync(shimPath);
s.shimPath = shimPath;
s.packsInstalled = existsSync(packsPath);
s.packsPath = packsPath;
s.cmdBackupExists = existsSync(bakPath);
if (existsSync(cmdPath)) {
s.cmdModified = readFileSync(cmdPath, "utf8").includes("i18n-shim.mjs");
}
s.ps1Installed = existsSync(ps1Path);
}
const cfg = readJson(CONFIG_PATH, null);
s.userConfig = cfg;
if (cfg) {
s.translationEnabled = cfg.enabled !== false;
s.userLanguage = cfg.language || null;
}
return s;
}
// ─── Auto-install on startup ──────────────────────────────────────────
try {
if (detectMcodeDir()) {
install();
} else {
log("auto-install skipped: mcode not found");
}
} catch (e) {
log("auto-install failed:", e.message);
}
// ─── MCP protocol (JSON-RPC 2.0 over stdio) ──────────────────────────
const TOOLS = [
{
name: "install",
description: "安装 mcode 汉化:复制 shim + 字典包到 mcode 目录,修改 mcode.cmd 注入 --import。已安装则跳过。可选传 mcodeDir 指定非标准位置(默认探测 PATH + npm global)。",
inputSchema: {
type: "object",
properties: {
mcodeDir: { type: "string", description: "mcode 安装目录(含 mcode.cmd 的目录)。非标准位置时手动指定。" },
},
additionalProperties: false,
},
},
{
name: "uninstall",
description: "卸载 mcode 汉化:恢复 mcode.cmd 备份,删除 shim + 字典包 + mcode.ps1。可选传 mcodeDir。",
inputSchema: {
type: "object",
properties: {
mcodeDir: { type: "string", description: "mcode 安装目录。省略时自动探测。" },
},
additionalProperties: false,
},
},
{
name: "switch",
description: "切换 mcode 显示语言。修改 ~/.mcode/config.json 的 language 字段,重启 mcode 生效。",
inputSchema: {
type: "object",
properties: {
locale: { type: "string", enum: ["zh-CN", "zh-TW", "en"], description: "目标语言" },
},
required: ["locale"],
additionalProperties: false,
},
},
{
name: "translate",
description: "翻译开关(独立于 language 切换)。true = 翻译生效,false = 不翻译。修改 ~/.mcode/config.json 的 enabled 字段,重启 mcode 生效。",
inputSchema: {
type: "object",
properties: {
on: { type: "boolean", description: "true 开启翻译,false 关闭翻译" },
},
required: ["on"],
additionalProperties: false,
},
},
{
name: "status",
description: "查看 mcode 汉化安装状态:shim/packs 是否就位,cmd 是否被改,用户配置等。可选传 mcodeDir 指定非标准位置(跟 install 一样)。",
inputSchema: {
type: "object",
properties: {
mcodeDir: { type: "string", description: "mcode 安装目录。省略时自动探测(或用上次记住的路径)。" },
},
additionalProperties: false,
},
},
];
let buffer = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", chunk => {
buffer += chunk;
let idx;
while ((idx = buffer.indexOf("\n")) !== -1) {
const line = buffer.slice(0, idx);
buffer = buffer.slice(idx + 1);
if (!line.trim()) continue;
try {
handleRequest(JSON.parse(line));
} catch (e) {
log("malformed request:", e.message);
}
}
});
function send(id, result) {
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
}
function sendError(id, code, message) {
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
}
function handleRequest(req) {
const { id, method, params } = req;
switch (method) {
case "initialize":
send(id, {
protocolVersion: "2024-11-05",
serverInfo: { name: "mcode-cli-zh", version: PLUGIN_VERSION },
capabilities: { tools: {} },
});
break;
case "notifications/initialized":
// no response
break;
case "tools/list":
send(id, { tools: TOOLS });
break;
case "tools/call":
try {
const { name, arguments: args = {} } = params || {};
let result;
switch (name) {
case "install": result = install(); break;
case "uninstall": result = uninstall(); break;
case "switch": result = switchLang(args); break;
case "translate": result = setEnabled(args); break;
case "status": result = getStatus(); break;
default: return sendError(id, -32602, `Unknown tool: ${name}`);
}
send(id, { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] });
} catch (e) {
sendError(id, -32603, e.message);
}
break;
default:
if (id !== undefined) sendError(id, -32601, `Method not found: ${method}`);
}
}