glosc-mcp 1.1.0 → 1.2.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/README.md +13 -1
- package/build/GloscMcp.js +93 -109
- package/build/GloscTools.js +125 -155
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -35,4 +35,16 @@
|
|
|
35
35
|
- 文本写入/编辑:按行添加/替换/删除(可批量),或创建/替换/删除整个文件
|
|
36
36
|
- 入参(概要):
|
|
37
37
|
- 整文件:`{ path, file: { action: "create"|"replace"|"delete", content?, overwrite? }, encoding?, newline?, ensureFinalNewline?, returnContent? }`
|
|
38
|
-
- 按行:`{ path, edits: [{ op: "add"|"replace"|"delete", ... }], createIfMissing?, encoding?, newline?, ensureFinalNewline?, returnContent? }`
|
|
38
|
+
- 按行:`{ path, edits: [{ op: "add"|"replace"|"delete", ... }], createIfMissing?, encoding?, newline?, ensureFinalNewline?, returnContent? }`
|
|
39
|
+
|
|
40
|
+
- `renameFile`
|
|
41
|
+
- 重命名文件(同目录改名)
|
|
42
|
+
- 入参:`{ path: string, newName: string, overwrite?: boolean }`
|
|
43
|
+
|
|
44
|
+
- `moveFile`
|
|
45
|
+
- 移动文件到新路径(必要时自动创建目录;目标为目录时会移动到该目录下)
|
|
46
|
+
- 入参:`{ from: string, to: string, overwrite?: boolean, createDirs?: boolean }`
|
|
47
|
+
|
|
48
|
+
- `listFilesRecursive`
|
|
49
|
+
- 递归获取文件夹中的所有文件(默认最多返回 5000 条,避免输出过大)
|
|
50
|
+
- 入参:`{ dir: string, limit?: number }`
|
package/build/GloscMcp.js
CHANGED
|
@@ -178,122 +178,106 @@ export class GloscMcp {
|
|
|
178
178
|
],
|
|
179
179
|
};
|
|
180
180
|
});
|
|
181
|
-
this.server.registerTool("
|
|
182
|
-
description: "
|
|
183
|
-
inputSchema: z
|
|
184
|
-
.
|
|
185
|
-
|
|
186
|
-
encoding: z
|
|
181
|
+
this.server.registerTool("renameFile", {
|
|
182
|
+
description: "重命名文件(同目录改名)",
|
|
183
|
+
inputSchema: z.object({
|
|
184
|
+
path: z.string().describe("源文件的绝对路径"),
|
|
185
|
+
newName: z
|
|
187
186
|
.string()
|
|
188
|
-
.describe("
|
|
189
|
-
|
|
190
|
-
newline: z
|
|
191
|
-
.enum(["auto", "lf", "crlf"])
|
|
192
|
-
.describe("换行符策略:auto/lf/crlf")
|
|
193
|
-
.default("auto"),
|
|
194
|
-
ensureFinalNewline: z
|
|
187
|
+
.describe("新文件名(仅文件名,不含路径)"),
|
|
188
|
+
overwrite: z
|
|
195
189
|
.boolean()
|
|
196
|
-
.describe("
|
|
190
|
+
.describe("目标已存在时是否覆盖")
|
|
197
191
|
.default(false),
|
|
198
|
-
|
|
192
|
+
}),
|
|
193
|
+
}, async ({ path, newName, overwrite }) => {
|
|
194
|
+
try {
|
|
195
|
+
const res = await GloscTools.renameFile({
|
|
196
|
+
path,
|
|
197
|
+
newName,
|
|
198
|
+
overwrite,
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: JSON.stringify(res, null, 2),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: `重命名失败: ${errorMessage}`,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
this.server.registerTool("moveFile", {
|
|
222
|
+
description: "移动文件到新路径(必要时自动创建目录)",
|
|
223
|
+
inputSchema: z.object({
|
|
224
|
+
from: z.string().describe("源文件的绝对路径"),
|
|
225
|
+
to: z.string().describe("目标路径(文件路径或目录)"),
|
|
226
|
+
overwrite: z
|
|
199
227
|
.boolean()
|
|
200
|
-
.describe("
|
|
228
|
+
.describe("目标已存在时是否覆盖")
|
|
201
229
|
.default(false),
|
|
202
|
-
|
|
230
|
+
createDirs: z
|
|
203
231
|
.boolean()
|
|
204
|
-
.describe("
|
|
205
|
-
.default(
|
|
206
|
-
file: z
|
|
207
|
-
.union([
|
|
208
|
-
z.object({
|
|
209
|
-
action: z
|
|
210
|
-
.literal("create")
|
|
211
|
-
.describe("新建文件"),
|
|
212
|
-
content: z.string().describe("文件内容"),
|
|
213
|
-
overwrite: z
|
|
214
|
-
.boolean()
|
|
215
|
-
.describe("文件已存在时是否覆盖")
|
|
216
|
-
.default(false),
|
|
217
|
-
}),
|
|
218
|
-
z.object({
|
|
219
|
-
action: z
|
|
220
|
-
.literal("replace")
|
|
221
|
-
.describe("替换整个文件内容"),
|
|
222
|
-
content: z.string().describe("文件内容"),
|
|
223
|
-
}),
|
|
224
|
-
z.object({
|
|
225
|
-
action: z
|
|
226
|
-
.literal("delete")
|
|
227
|
-
.describe("删除文件"),
|
|
228
|
-
}),
|
|
229
|
-
])
|
|
230
|
-
.optional(),
|
|
231
|
-
edits: z
|
|
232
|
-
.array(z.union([
|
|
233
|
-
z.object({
|
|
234
|
-
op: z.literal("add"),
|
|
235
|
-
at: z
|
|
236
|
-
.number()
|
|
237
|
-
.int()
|
|
238
|
-
.min(1)
|
|
239
|
-
.describe("插入位置行号(1-based);允许 lineCount+1 表示追加"),
|
|
240
|
-
position: z
|
|
241
|
-
.enum(["before", "after"])
|
|
242
|
-
.describe("插入到该行之前或之后")
|
|
243
|
-
.default("before"),
|
|
244
|
-
lines: z
|
|
245
|
-
.array(z.string())
|
|
246
|
-
.min(1)
|
|
247
|
-
.describe("要插入的行(不含换行符)"),
|
|
248
|
-
}),
|
|
249
|
-
z.object({
|
|
250
|
-
op: z.literal("replace"),
|
|
251
|
-
start: z
|
|
252
|
-
.number()
|
|
253
|
-
.int()
|
|
254
|
-
.min(1)
|
|
255
|
-
.describe("起始行号(1-based)"),
|
|
256
|
-
end: z
|
|
257
|
-
.number()
|
|
258
|
-
.int()
|
|
259
|
-
.min(1)
|
|
260
|
-
.optional()
|
|
261
|
-
.describe("结束行号(1-based,含);缺省则等于 start"),
|
|
262
|
-
lines: z
|
|
263
|
-
.array(z.string())
|
|
264
|
-
.describe("替换后的行(可多行)"),
|
|
265
|
-
}),
|
|
266
|
-
z.object({
|
|
267
|
-
op: z.literal("delete"),
|
|
268
|
-
start: z
|
|
269
|
-
.number()
|
|
270
|
-
.int()
|
|
271
|
-
.min(1)
|
|
272
|
-
.describe("起始行号(1-based)"),
|
|
273
|
-
end: z
|
|
274
|
-
.number()
|
|
275
|
-
.int()
|
|
276
|
-
.min(1)
|
|
277
|
-
.optional()
|
|
278
|
-
.describe("结束行号(1-based,含);缺省则等于 start"),
|
|
279
|
-
}),
|
|
280
|
-
]))
|
|
281
|
-
.optional(),
|
|
282
|
-
})
|
|
283
|
-
.refine((v) => !!v.file !== !!v.edits, {
|
|
284
|
-
message: "必须且只能提供 file 或 edits 之一",
|
|
232
|
+
.describe("是否自动创建目标目录")
|
|
233
|
+
.default(true),
|
|
285
234
|
}),
|
|
286
|
-
}, async (
|
|
235
|
+
}, async ({ from, to, overwrite, createDirs }) => {
|
|
236
|
+
try {
|
|
237
|
+
const res = await GloscTools.moveFile({
|
|
238
|
+
from,
|
|
239
|
+
to,
|
|
240
|
+
overwrite,
|
|
241
|
+
createDirs,
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: JSON.stringify(res, null, 2),
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: `移动失败: ${errorMessage}`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
this.server.registerTool("listFilesRecursive", {
|
|
265
|
+
description: "递归获取文件夹中的所有文件",
|
|
266
|
+
inputSchema: z.object({
|
|
267
|
+
dir: z.string().describe("目录的绝对路径"),
|
|
268
|
+
limit: z
|
|
269
|
+
.number()
|
|
270
|
+
.int()
|
|
271
|
+
.min(1)
|
|
272
|
+
.max(20000)
|
|
273
|
+
.describe("最多返回多少条(防止输出过大)")
|
|
274
|
+
.default(5000),
|
|
275
|
+
}),
|
|
276
|
+
}, async ({ dir, limit }) => {
|
|
287
277
|
try {
|
|
288
|
-
const res = await GloscTools.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
newline: input.newline,
|
|
292
|
-
ensureFinalNewline: input.ensureFinalNewline,
|
|
293
|
-
returnContent: input.returnContent,
|
|
294
|
-
createIfMissing: input.createIfMissing,
|
|
295
|
-
file: input.file,
|
|
296
|
-
edits: input.edits,
|
|
278
|
+
const res = await GloscTools.listFilesRecursive({
|
|
279
|
+
dir,
|
|
280
|
+
limit,
|
|
297
281
|
});
|
|
298
282
|
return {
|
|
299
283
|
content: [
|
|
@@ -310,7 +294,7 @@ export class GloscMcp {
|
|
|
310
294
|
content: [
|
|
311
295
|
{
|
|
312
296
|
type: "text",
|
|
313
|
-
text:
|
|
297
|
+
text: `列出文件失败: ${errorMessage}`,
|
|
314
298
|
},
|
|
315
299
|
],
|
|
316
300
|
};
|
package/build/GloscTools.js
CHANGED
|
@@ -508,184 +508,154 @@ if ($null -ne $apps) {
|
|
|
508
508
|
// 支持 \n / \r\n,且保留末尾空行(比如文件以换行结尾)
|
|
509
509
|
if (text === "")
|
|
510
510
|
return [""];
|
|
511
|
-
|
|
512
|
-
return lines;
|
|
511
|
+
return text.split(/\r?\n/);
|
|
513
512
|
}
|
|
514
513
|
static joinLines(lines, newline) {
|
|
515
514
|
return lines.join(newline);
|
|
516
515
|
}
|
|
517
|
-
static async
|
|
518
|
-
const filePath = options.path?.trim();
|
|
519
|
-
if (!filePath)
|
|
520
|
-
throw new Error("path 不能为空");
|
|
521
|
-
const encoding = options.encoding ?? "utf8";
|
|
522
|
-
const returnContent = options.returnContent ?? false;
|
|
523
|
-
const createIfMissing = options.createIfMissing ?? false;
|
|
524
|
-
const hasFileAction = !!options.file;
|
|
525
|
-
const hasEdits = Array.isArray(options.edits) && options.edits.length > 0;
|
|
526
|
-
if ((hasFileAction && hasEdits) || (!hasFileAction && !hasEdits)) {
|
|
527
|
-
throw new Error("必须且只能提供 file 或 edits 之一");
|
|
528
|
-
}
|
|
529
|
-
let existedBefore = true;
|
|
530
|
-
let originalBuffer = null;
|
|
516
|
+
static async pathExists(p) {
|
|
531
517
|
try {
|
|
532
|
-
|
|
518
|
+
await fs.stat(p);
|
|
519
|
+
return true;
|
|
533
520
|
}
|
|
534
521
|
catch (e) {
|
|
522
|
+
if (e && (e.code === "ENOENT" || e.code === "ENOTDIR"))
|
|
523
|
+
return false;
|
|
524
|
+
throw e;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
static async removePath(targetPath) {
|
|
528
|
+
const st = await fs.lstat(targetPath);
|
|
529
|
+
if (st.isDirectory()) {
|
|
530
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
await fs.unlink(targetPath);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
static async movePathInternal(options) {
|
|
537
|
+
const from = options.from?.trim();
|
|
538
|
+
const to = options.to?.trim();
|
|
539
|
+
if (!from)
|
|
540
|
+
throw new Error("from 不能为空");
|
|
541
|
+
if (!to)
|
|
542
|
+
throw new Error("to 不能为空");
|
|
543
|
+
const overwrite = options.overwrite ?? false;
|
|
544
|
+
const createDirs = options.createDirs ?? true;
|
|
545
|
+
const fromStat = await fs.lstat(from).catch((e) => {
|
|
535
546
|
if (e && (e.code === "ENOENT" || e.code === "ENOTDIR")) {
|
|
536
|
-
|
|
537
|
-
originalBuffer = null;
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
throw e;
|
|
547
|
+
throw new Error("源路径不存在");
|
|
541
548
|
}
|
|
549
|
+
throw e;
|
|
550
|
+
});
|
|
551
|
+
let dest = to;
|
|
552
|
+
const toLooksLikeDir = /[\\/]+$/.test(to);
|
|
553
|
+
if (toLooksLikeDir) {
|
|
554
|
+
dest = path.join(to, path.basename(from));
|
|
542
555
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if (
|
|
547
|
-
|
|
556
|
+
else {
|
|
557
|
+
try {
|
|
558
|
+
const toStat = await fs.lstat(to);
|
|
559
|
+
if (toStat.isDirectory()) {
|
|
560
|
+
dest = path.join(to, path.basename(from));
|
|
548
561
|
}
|
|
549
|
-
return {
|
|
550
|
-
ok: true,
|
|
551
|
-
path: filePath,
|
|
552
|
-
action: "delete",
|
|
553
|
-
existedBefore,
|
|
554
|
-
};
|
|
555
562
|
}
|
|
556
|
-
|
|
557
|
-
if (
|
|
558
|
-
throw
|
|
563
|
+
catch (e) {
|
|
564
|
+
if (!(e && (e.code === "ENOENT" || e.code === "ENOTDIR"))) {
|
|
565
|
+
throw e;
|
|
559
566
|
}
|
|
560
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
561
|
-
const newline = GloscTools.normalizeNewlineOption(options.newline, undefined);
|
|
562
|
-
let content = options.file.content ?? "";
|
|
563
|
-
if (options.ensureFinalNewline) {
|
|
564
|
-
if (!content.endsWith("\n") && !content.endsWith("\r\n")) {
|
|
565
|
-
content += newline;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
const outBuffer = iconv.encode(content, encoding);
|
|
569
|
-
await fs.writeFile(filePath, outBuffer);
|
|
570
|
-
return {
|
|
571
|
-
ok: true,
|
|
572
|
-
path: filePath,
|
|
573
|
-
action: existedBefore ? "replace" : "create",
|
|
574
|
-
existedBefore,
|
|
575
|
-
bytesWritten: outBuffer.length,
|
|
576
|
-
content: returnContent ? content : undefined,
|
|
577
|
-
};
|
|
578
567
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
: undefined;
|
|
584
|
-
const newline = GloscTools.normalizeNewlineOption(options.newline, decodedExisting);
|
|
585
|
-
let content = options.file.content ?? "";
|
|
586
|
-
if (options.ensureFinalNewline) {
|
|
587
|
-
if (!content.endsWith("\n") && !content.endsWith("\r\n")) {
|
|
588
|
-
content += newline;
|
|
589
|
-
}
|
|
568
|
+
}
|
|
569
|
+
if (await GloscTools.pathExists(dest)) {
|
|
570
|
+
if (!overwrite) {
|
|
571
|
+
throw new Error("目标已存在;如需覆盖请设置 overwrite=true");
|
|
590
572
|
}
|
|
591
|
-
|
|
592
|
-
await fs.writeFile(filePath, outBuffer);
|
|
593
|
-
return {
|
|
594
|
-
ok: true,
|
|
595
|
-
path: filePath,
|
|
596
|
-
action: "replace",
|
|
597
|
-
existedBefore,
|
|
598
|
-
bytesWritten: outBuffer.length,
|
|
599
|
-
content: returnContent ? content : undefined,
|
|
600
|
-
};
|
|
573
|
+
await GloscTools.removePath(dest);
|
|
601
574
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (originalBuffer != null) {
|
|
605
|
-
text = iconv.decode(originalBuffer, encoding);
|
|
575
|
+
if (createDirs) {
|
|
576
|
+
await fs.mkdir(path.dirname(dest), { recursive: true });
|
|
606
577
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const at = Math.trunc(edit.at);
|
|
623
|
-
if (!Number.isFinite(at) || at < 1) {
|
|
624
|
-
throw new Error("add.at 必须为 >=1 的整数");
|
|
625
|
-
}
|
|
626
|
-
// 允许 at = lines.length + 1(before)用于追加到末尾
|
|
627
|
-
const maxAt = lines.length + 1;
|
|
628
|
-
if (at > maxAt) {
|
|
629
|
-
throw new Error(`add.at 超出范围:当前最大允许 ${maxAt}(可用 lineCount+1 追加)`);
|
|
578
|
+
try {
|
|
579
|
+
await fs.rename(from, dest);
|
|
580
|
+
return { ok: true, from, to: dest };
|
|
581
|
+
}
|
|
582
|
+
catch (e) {
|
|
583
|
+
// 跨盘符/设备移动在部分平台会抛 EXDEV
|
|
584
|
+
if (e && e.code === "EXDEV") {
|
|
585
|
+
if (fromStat.isDirectory()) {
|
|
586
|
+
const cp = fs.cp;
|
|
587
|
+
if (!cp) {
|
|
588
|
+
throw new Error("跨设备移动目录失败(EXDEV),且当前 Node 版本不支持 fs.cp");
|
|
589
|
+
}
|
|
590
|
+
await cp(from, dest, { recursive: true, force: overwrite });
|
|
591
|
+
await fs.rm(from, { recursive: true, force: true });
|
|
592
|
+
return { ok: true, from, to: dest };
|
|
630
593
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
lines.splice(insertIndex, 0, ...edit.lines);
|
|
594
|
+
await fs.copyFile(from, dest);
|
|
595
|
+
await fs.unlink(from);
|
|
596
|
+
return { ok: true, from, to: dest };
|
|
635
597
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
598
|
+
throw e;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
static async renameFile(options) {
|
|
602
|
+
const from = options.path?.trim();
|
|
603
|
+
const newName = options.newName?.trim();
|
|
604
|
+
if (!from)
|
|
605
|
+
throw new Error("path 不能为空");
|
|
606
|
+
if (!newName)
|
|
607
|
+
throw new Error("newName 不能为空");
|
|
608
|
+
// 限制为“同目录改名”,避免把 rename 当 move 用
|
|
609
|
+
const base = path.basename(newName);
|
|
610
|
+
if (base !== newName) {
|
|
611
|
+
throw new Error("newName 只能是文件名,不能包含路径分隔符");
|
|
612
|
+
}
|
|
613
|
+
const to = path.join(path.dirname(from), newName);
|
|
614
|
+
return GloscTools.movePathInternal({
|
|
615
|
+
from,
|
|
616
|
+
to,
|
|
617
|
+
overwrite: options.overwrite,
|
|
618
|
+
createDirs: true,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
static async moveFile(options) {
|
|
622
|
+
return GloscTools.movePathInternal(options);
|
|
623
|
+
}
|
|
624
|
+
static async listFilesRecursive(options) {
|
|
625
|
+
const dir = options.dir?.trim();
|
|
626
|
+
if (!dir)
|
|
627
|
+
throw new Error("dir 不能为空");
|
|
628
|
+
const limit = Math.max(1, Math.trunc(options.limit ?? 5000));
|
|
629
|
+
const rootStat = await fs.lstat(dir).catch((e) => {
|
|
630
|
+
if (e && (e.code === "ENOENT" || e.code === "ENOTDIR")) {
|
|
631
|
+
throw new Error("目录不存在");
|
|
649
632
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
633
|
+
throw e;
|
|
634
|
+
});
|
|
635
|
+
if (!rootStat.isDirectory()) {
|
|
636
|
+
throw new Error("dir 必须是目录");
|
|
637
|
+
}
|
|
638
|
+
const files = [];
|
|
639
|
+
const stack = [dir];
|
|
640
|
+
let truncated = false;
|
|
641
|
+
while (stack.length > 0) {
|
|
642
|
+
const current = stack.pop();
|
|
643
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
644
|
+
for (const entry of entries) {
|
|
645
|
+
const full = path.join(current, entry.name);
|
|
646
|
+
if (entry.isDirectory()) {
|
|
647
|
+
stack.push(full);
|
|
658
648
|
}
|
|
659
|
-
if (
|
|
660
|
-
|
|
649
|
+
else if (entry.isFile()) {
|
|
650
|
+
files.push(full);
|
|
651
|
+
if (files.length >= limit) {
|
|
652
|
+
truncated = true;
|
|
653
|
+
stack.length = 0;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
661
656
|
}
|
|
662
|
-
lines.splice(start - 1, end - start + 1);
|
|
663
|
-
if (lines.length === 0)
|
|
664
|
-
lines = [""];
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
const neverEdit = edit;
|
|
668
|
-
throw new Error(`未知 edit.op: ${neverEdit.op}`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
let outText = GloscTools.joinLines(lines, newline);
|
|
672
|
-
if (options.ensureFinalNewline) {
|
|
673
|
-
if (!outText.endsWith("\n") && !outText.endsWith("\r\n")) {
|
|
674
|
-
outText += newline;
|
|
675
657
|
}
|
|
676
658
|
}
|
|
677
|
-
|
|
678
|
-
await fs.writeFile(filePath, outBuffer);
|
|
679
|
-
return {
|
|
680
|
-
ok: true,
|
|
681
|
-
path: filePath,
|
|
682
|
-
action: "edit",
|
|
683
|
-
existedBefore,
|
|
684
|
-
lineCountBefore,
|
|
685
|
-
lineCountAfter: lines.length,
|
|
686
|
-
editsApplied: edits.length,
|
|
687
|
-
bytesWritten: outBuffer.length,
|
|
688
|
-
content: returnContent ? outText : undefined,
|
|
689
|
-
};
|
|
659
|
+
return { ok: true, dir, files, truncated };
|
|
690
660
|
}
|
|
691
661
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glosc-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,11 +34,9 @@
|
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
35
35
|
"cheerio": "^1.1.2",
|
|
36
36
|
"csv-parser": "^3.2.0",
|
|
37
|
-
"iconv-lite": "^0.7.1",
|
|
38
37
|
"mammoth": "^1.11.0",
|
|
39
38
|
"pdf-parse": "^2.4.5",
|
|
40
39
|
"playwright": "^1.57.0",
|
|
41
|
-
"sharp": "^0.34.5",
|
|
42
40
|
"xlsx": "^0.18.5",
|
|
43
41
|
"zod": "^4.2.1"
|
|
44
42
|
},
|