opencode-gbk-tools 0.1.7 → 0.1.9
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 +88 -106
- package/dist/opencode-tools/gbk_edit.js +9 -1
- package/dist/opencode-tools/gbk_write.js +54 -3
- package/dist/plugin/index.js +63 -4
- package/dist/release-manifest.json +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,158 +1,140 @@
|
|
|
1
1
|
# opencode-gbk-tools
|
|
2
2
|
|
|
3
|
-
为 OpenCode 提供一套面向 `GBK` / `GB18030`
|
|
3
|
+
为 OpenCode 提供一套面向 `GBK` / `GB18030` 编码文本文件的自定义工具与专用 agent。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
解决 OpenCode 内置工具无法正确读写 GBK 编码文件的问题。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- `gbk_write`
|
|
9
|
-
- `gbk_edit`
|
|
10
|
-
- `gbk-engine` agent
|
|
7
|
+
---
|
|
11
8
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
推荐方式:直接作为 OpenCode npm plugin 加载工具。
|
|
15
|
-
|
|
16
|
-
最方便的一键接入:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npx opencode-gbk-tools setup --project
|
|
20
|
-
```
|
|
9
|
+
## 提供的工具
|
|
21
10
|
|
|
22
|
-
|
|
11
|
+
| 工具 | 用途 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| `gbk_read` | 读取 GBK/GB18030 文件,支持分页、尾部预览 |
|
|
14
|
+
| `gbk_write` | 写入或追加内容到 GBK 文件(`append=true` 支持追加) |
|
|
15
|
+
| `gbk_edit` | 精确替换 GBK 文件中的指定文本块 |
|
|
16
|
+
| `gbk_search` | 在 GBK 文件中搜索关键词,返回行号和上下文 |
|
|
17
|
+
| `gbk-engine` | 专用 agent,引导 AI 正确使用上述工具 |
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
- 自动加入 `plugin: ["opencode-gbk-tools"]`
|
|
26
|
-
- 安装 `.opencode/agents/gbk-engine.md`
|
|
19
|
+
---
|
|
27
20
|
|
|
28
|
-
|
|
21
|
+
## 安装
|
|
29
22
|
|
|
30
23
|
```bash
|
|
31
|
-
npx opencode-gbk-tools
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`opencode.json`:
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"$schema": "https://opencode.ai/config.json",
|
|
39
|
-
"plugin": ["opencode-gbk-tools"]
|
|
40
|
-
}
|
|
24
|
+
npx opencode-gbk-tools install
|
|
41
25
|
```
|
|
42
26
|
|
|
43
|
-
|
|
27
|
+
安装完成后**重启 OpenCode**,工具立即生效。
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
- `gbk_write`
|
|
47
|
-
- `gbk_edit`
|
|
29
|
+
---
|
|
48
30
|
|
|
49
|
-
|
|
31
|
+
## 卸载
|
|
50
32
|
|
|
51
|
-
|
|
33
|
+
### 一键卸载(对应一键安装)
|
|
52
34
|
|
|
53
|
-
|
|
35
|
+
如果当初是用 `npx opencode-gbk-tools install` 全局安装的,执行:
|
|
54
36
|
|
|
55
37
|
```bash
|
|
56
|
-
npx opencode-gbk-tools
|
|
38
|
+
npx opencode-gbk-tools uninstall
|
|
57
39
|
```
|
|
58
40
|
|
|
59
|
-
|
|
41
|
+
---
|
|
60
42
|
|
|
61
|
-
|
|
62
|
-
npm install -g opencode-gbk-tools
|
|
63
|
-
opencode-gbk install
|
|
64
|
-
opencode-gbk setup
|
|
65
|
-
```
|
|
43
|
+
## 工具使用说明
|
|
66
44
|
|
|
67
|
-
|
|
45
|
+
### gbk_read — 读取文件
|
|
68
46
|
|
|
69
|
-
```
|
|
70
|
-
|
|
47
|
+
```
|
|
48
|
+
gbk_read(filePath="文件路径")
|
|
49
|
+
gbk_read(filePath="文件路径", offset=100, limit=50) # 从第100行读取50行
|
|
50
|
+
gbk_read(filePath="文件路径", tail=true, limit=30) # 读取最后30行
|
|
71
51
|
```
|
|
72
52
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- 全局安装目标:`~/.config/opencode`
|
|
76
|
-
- 项目安装目标:当前命令执行目录下的 `.opencode`
|
|
53
|
+
- 返回带行号的内容,格式为 `行号: 内容`
|
|
54
|
+
- `limit` 建议不超过 500 行,大文件请先用 `gbk_search` 定位
|
|
77
55
|
|
|
78
|
-
|
|
56
|
+
### gbk_search — 搜索内容
|
|
79
57
|
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
```
|
|
59
|
+
gbk_search(filePath="文件路径", pattern="搜索关键词")
|
|
60
|
+
gbk_search(filePath="文件路径", pattern="[@标签名]", contextLines=5)
|
|
61
|
+
```
|
|
83
62
|
|
|
84
|
-
|
|
85
|
-
opencode-gbk install --project
|
|
86
|
-
opencode-gbk install --force
|
|
63
|
+
- 返回匹配行号及上下文,是处理大文件的第一步
|
|
87
64
|
|
|
88
|
-
|
|
89
|
-
opencode-gbk uninstall --project
|
|
65
|
+
### gbk_edit — 编辑文件
|
|
90
66
|
|
|
91
|
-
|
|
92
|
-
|
|
67
|
+
```
|
|
68
|
+
gbk_edit(filePath="文件路径", oldString="原文内容", newString="新内容")
|
|
69
|
+
gbk_edit(filePath="文件路径", oldString="原文", newString="新文", startLine=100, endLine=200)
|
|
93
70
|
```
|
|
94
71
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
如果你是通过 `setup` 或手动修改 `opencode.json` / `opencode.jsonc` 启用 npm plugin:
|
|
98
|
-
|
|
99
|
-
- 删除 `plugin` 数组中的 `"opencode-gbk-tools"`
|
|
100
|
-
- 如果不再需要 `gbk-engine`,再删除对应 agent 文件
|
|
101
|
-
- 项目级:`.opencode/agents/gbk-engine.md`
|
|
102
|
-
- 全局:`~/.config/opencode/agents/gbk-engine.md`
|
|
72
|
+
**重要:`oldString` 必须是文件的原始内容,不能包含 `gbk_read` 输出的行号前缀。**
|
|
103
73
|
|
|
104
|
-
|
|
74
|
+
错误示范(包含行号前缀,会失败):
|
|
75
|
+
```
|
|
76
|
+
oldString="3787: SENDMSG 0 内容"
|
|
77
|
+
```
|
|
105
78
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
79
|
+
正确示范(纯文件内容):
|
|
80
|
+
```
|
|
81
|
+
oldString="SENDMSG 0 内容"
|
|
109
82
|
```
|
|
110
83
|
|
|
111
|
-
|
|
84
|
+
### gbk_write — 写入文件
|
|
112
85
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
86
|
+
```
|
|
87
|
+
gbk_write(filePath="文件路径", content="内容", overwrite=true) # 覆盖写入
|
|
88
|
+
gbk_write(filePath="文件路径", content="\r\n新增内容", append=true) # 追加到末尾
|
|
89
|
+
```
|
|
116
90
|
|
|
117
|
-
|
|
91
|
+
- `append=true`:在文件末尾追加内容,文件不存在时自动创建
|
|
92
|
+
- `overwrite=true`:覆盖整个文件
|
|
118
93
|
|
|
119
|
-
|
|
94
|
+
---
|
|
120
95
|
|
|
121
|
-
|
|
96
|
+
## 大文件操作建议
|
|
122
97
|
|
|
123
|
-
|
|
124
|
-
- `setup` 会自动写入 `plugin` 配置并安装 `gbk-engine`
|
|
125
|
-
- `gbk-engine` 仍通过 CLI 安装到 `.opencode/agents/` 或 `~/.config/opencode/agents/`
|
|
126
|
-
- 如果你只需要工具,不需要 agent,只配置 `plugin` 即可
|
|
98
|
+
对于行数超过 500 行的大文件,推荐以下工作流:
|
|
127
99
|
|
|
128
|
-
|
|
100
|
+
```
|
|
101
|
+
1. gbk_search(pattern="要找的内容") → 获取行号
|
|
102
|
+
2. gbk_read(offset=行号-5, limit=20) → 读取目标区域
|
|
103
|
+
3. gbk_edit(oldString=..., newString=..., startLine=..., endLine=...) → 精确编辑
|
|
104
|
+
```
|
|
129
105
|
|
|
130
|
-
|
|
131
|
-
- 文件内容写入必须走 `gbk_write`
|
|
132
|
-
- 文件内容修改必须走 `gbk_edit`
|
|
133
|
-
- 内置 `read`、`edit`、`grep` 被限制
|
|
106
|
+
---
|
|
134
107
|
|
|
135
108
|
## 已知限制
|
|
136
109
|
|
|
137
110
|
- 只支持文本文件,不支持二进制文件
|
|
138
|
-
-
|
|
139
|
-
- 只支持 `gbk` 和 `gb18030`
|
|
140
|
-
- `edit: deny` 在 OpenCode 中会一起限制内置 `write`、`patch`、`multiedit`
|
|
111
|
+
- 只支持 `gbk` 和 `gb18030` 编码
|
|
141
112
|
- 对无法映射的字符沿用 `iconv-lite` 默认替代行为
|
|
142
113
|
|
|
143
|
-
|
|
114
|
+
---
|
|
144
115
|
|
|
145
|
-
|
|
146
|
-
- `gbk_read` 返回 `fileSize`、`newlineStyle`、`streamed`、`truncated`
|
|
147
|
-
- `gbk_edit` 支持 `startLine/endLine`
|
|
148
|
-
- `gbk_edit` 支持 `startAnchor/endAnchor`
|
|
149
|
-
- 大文件会自动走更省内存的流式路径
|
|
116
|
+
## 常见问题
|
|
150
117
|
|
|
151
|
-
|
|
118
|
+
**Q:安装后 OpenCode 里看不到工具?**
|
|
119
|
+
A:重启 OpenCode。工具在 OpenCode 启动时加载,安装后需要重启才生效。
|
|
152
120
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
121
|
+
**Q:`npx` 提示找不到命令?**
|
|
122
|
+
A:请先安装 Node.js(https://nodejs.org/),版本需要 18 或以上。
|
|
123
|
+
|
|
124
|
+
**Q:Windows 路径在哪?**
|
|
125
|
+
A:全局安装目录为 `C:\Users\你的用户名\.config\opencode\tools\`
|
|
126
|
+
|
|
127
|
+
**Q:gbk_edit 提示"未找到需要替换的文本"?**
|
|
128
|
+
A:检查 `oldString` 是否包含了行号前缀(如 `"3787: "`),去掉行号前缀后重试。若要在文件末尾追加内容,请使用 `gbk_write [append=true]`。
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 版本历史
|
|
133
|
+
|
|
134
|
+
| 版本 | 说明 |
|
|
135
|
+
|------|------|
|
|
136
|
+
| 0.1.9 | `gbk_edit` 修复精确匹配路径写入 CRLF 文件时换行风格变 mixed 的 bug |
|
|
137
|
+
| 0.1.8 | `gbk_write` 新增 `append=true` 追加模式 |
|
|
138
|
+
| 0.1.7 | `gbk_edit` 自动剥离行号前缀后重试匹配 |
|
|
139
|
+
| 0.1.6 | 新增 `gbk_search` 搜索工具 |
|
|
140
|
+
| 0.1.5 | 初始版本 |
|
|
@@ -16724,7 +16724,9 @@ async function replaceGbkFileText(input) {
|
|
|
16724
16724
|
} else if (occurrencesBefore > 1) {
|
|
16725
16725
|
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${normalizedInput.oldString}`);
|
|
16726
16726
|
}
|
|
16727
|
-
const
|
|
16727
|
+
const fileNewlineStyle = detectNewlineStyle(current.content);
|
|
16728
|
+
const alignedNewString = fileNewlineStyle === "crlf" ? normalizedInput.newString.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : fileNewlineStyle === "lf" ? normalizedInput.newString.replace(/\r\n/g, "\n") : normalizedInput.newString;
|
|
16729
|
+
const replaced = replaceAll ? scope.selectedText.split(normalizedInput.oldString).join(alignedNewString) : scope.selectedText.replace(normalizedInput.oldString, alignedNewString);
|
|
16728
16730
|
const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
|
|
16729
16731
|
const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
|
|
16730
16732
|
await fs2.writeFile(current.filePath, buffer);
|
|
@@ -16750,6 +16752,12 @@ gbk_read output looks like "3787: SENDMSG 0 content". The "3787: " is a navigati
|
|
|
16750
16752
|
oldString must be the raw file content: "SENDMSG 0 content" (no line number prefix).
|
|
16751
16753
|
Including line numbers in oldString will cause GBK_NO_MATCH even if the content exists.
|
|
16752
16754
|
|
|
16755
|
+
CRITICAL \u2014 do NOT use bare newlines or whitespace-only as oldString:
|
|
16756
|
+
Using oldString="
|
|
16757
|
+
" or oldString="" to append content will always fail.
|
|
16758
|
+
To append content to the END of a file, use gbk_write with append=true instead:
|
|
16759
|
+
gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
|
|
16760
|
+
|
|
16753
16761
|
Recommended workflow for large files (when gbk_read returned truncated=true):
|
|
16754
16762
|
1. gbk_search(pattern) \u2192 find exact lineNumber
|
|
16755
16763
|
2. gbk_read(offset=<lineNumber>, limit=20) \u2192 get the exact block (strip "N: " prefixes)
|
|
@@ -16314,9 +16314,49 @@ async function writeGbkFile(input) {
|
|
|
16314
16314
|
const encoding = input.encoding ?? "gbk";
|
|
16315
16315
|
const createDirectories = input.createDirectories ?? true;
|
|
16316
16316
|
const overwrite = input.overwrite ?? false;
|
|
16317
|
+
const append = input.append ?? false;
|
|
16317
16318
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
16318
16319
|
const parent = path2.dirname(candidatePath);
|
|
16319
16320
|
assertEncodingSupported(encoding);
|
|
16321
|
+
if (append) {
|
|
16322
|
+
try {
|
|
16323
|
+
const parentStat = await fs2.stat(parent);
|
|
16324
|
+
if (!parentStat.isDirectory()) {
|
|
16325
|
+
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
16326
|
+
}
|
|
16327
|
+
} catch (error45) {
|
|
16328
|
+
if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
|
|
16329
|
+
if (!createDirectories) {
|
|
16330
|
+
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
16331
|
+
}
|
|
16332
|
+
await fs2.mkdir(parent, { recursive: true });
|
|
16333
|
+
} else if (error45 instanceof Error && "code" in error45) {
|
|
16334
|
+
throw error45;
|
|
16335
|
+
}
|
|
16336
|
+
}
|
|
16337
|
+
let existingContent = "";
|
|
16338
|
+
let existed = false;
|
|
16339
|
+
try {
|
|
16340
|
+
const existingBuffer = await fs2.readFile(candidatePath);
|
|
16341
|
+
existingContent = import_iconv_lite.default.decode(existingBuffer, encoding);
|
|
16342
|
+
existed = true;
|
|
16343
|
+
} catch (error45) {
|
|
16344
|
+
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
16345
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
|
|
16346
|
+
}
|
|
16347
|
+
}
|
|
16348
|
+
const combined = existingContent + input.content;
|
|
16349
|
+
const buffer = import_iconv_lite.default.encode(combined, encoding);
|
|
16350
|
+
await fs2.writeFile(candidatePath, buffer);
|
|
16351
|
+
return {
|
|
16352
|
+
filePath: candidatePath,
|
|
16353
|
+
encoding,
|
|
16354
|
+
bytesWritten: buffer.byteLength,
|
|
16355
|
+
created: !existed,
|
|
16356
|
+
overwritten: false,
|
|
16357
|
+
appended: true
|
|
16358
|
+
};
|
|
16359
|
+
}
|
|
16320
16360
|
try {
|
|
16321
16361
|
const stat = await fs2.stat(candidatePath);
|
|
16322
16362
|
if (stat.isDirectory()) {
|
|
@@ -16370,13 +16410,24 @@ async function writeGbkFile(input) {
|
|
|
16370
16410
|
|
|
16371
16411
|
// src/tools/gbk_write.ts
|
|
16372
16412
|
var gbk_write_default = tool({
|
|
16373
|
-
description:
|
|
16413
|
+
description: `Write GBK encoded text files.
|
|
16414
|
+
|
|
16415
|
+
**append=true** (recommended for adding content to existing files):
|
|
16416
|
+
- Reads the existing file content and appends new content at the end.
|
|
16417
|
+
- Works whether the file exists or not (creates it if missing).
|
|
16418
|
+
- Use this whenever you want to add lines/content to an existing GBK file.
|
|
16419
|
+
- Example: gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
|
|
16420
|
+
|
|
16421
|
+
**overwrite=true**: Replace the entire file with new content.
|
|
16422
|
+
|
|
16423
|
+
**Default (overwrite=false, append=false)**: Fails if the file already exists.`,
|
|
16374
16424
|
args: {
|
|
16375
16425
|
filePath: tool.schema.string().describe("Target file path"),
|
|
16376
|
-
content: tool.schema.string().describe("Text content to write"),
|
|
16426
|
+
content: tool.schema.string().describe("Text content to write (or append)"),
|
|
16377
16427
|
encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
|
|
16378
16428
|
createDirectories: tool.schema.boolean().optional().describe("Create parent directories"),
|
|
16379
|
-
overwrite: tool.schema.boolean().optional().describe("Overwrite existing file"),
|
|
16429
|
+
overwrite: tool.schema.boolean().optional().describe("Overwrite existing file (replaces entire content)"),
|
|
16430
|
+
append: tool.schema.boolean().optional().describe("Append content to existing file instead of overwriting; creates the file if it does not exist"),
|
|
16380
16431
|
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
16381
16432
|
},
|
|
16382
16433
|
async execute(args, context) {
|
package/dist/plugin/index.js
CHANGED
|
@@ -16934,7 +16934,9 @@ async function replaceGbkFileText(input) {
|
|
|
16934
16934
|
} else if (occurrencesBefore > 1) {
|
|
16935
16935
|
throw createGbkError("GBK_MULTIPLE_MATCHES", `\u627E\u5230\u591A\u4E2A\u5339\u914D\u9879: ${normalizedInput.oldString}`);
|
|
16936
16936
|
}
|
|
16937
|
-
const
|
|
16937
|
+
const fileNewlineStyle = detectNewlineStyle(current.content);
|
|
16938
|
+
const alignedNewString = fileNewlineStyle === "crlf" ? normalizedInput.newString.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n") : fileNewlineStyle === "lf" ? normalizedInput.newString.replace(/\r\n/g, "\n") : normalizedInput.newString;
|
|
16939
|
+
const replaced = replaceAll ? scope.selectedText.split(normalizedInput.oldString).join(alignedNewString) : scope.selectedText.replace(normalizedInput.oldString, alignedNewString);
|
|
16938
16940
|
const outputText = `${current.content.slice(0, scope.rangeStart)}${replaced}${current.content.slice(scope.rangeEnd)}`;
|
|
16939
16941
|
const buffer = import_iconv_lite.default.encode(outputText, current.encoding);
|
|
16940
16942
|
await fs2.writeFile(current.filePath, buffer);
|
|
@@ -16984,9 +16986,49 @@ async function writeGbkFile(input) {
|
|
|
16984
16986
|
const encoding = input.encoding ?? "gbk";
|
|
16985
16987
|
const createDirectories = input.createDirectories ?? true;
|
|
16986
16988
|
const overwrite = input.overwrite ?? false;
|
|
16989
|
+
const append = input.append ?? false;
|
|
16987
16990
|
const { candidatePath } = await assertPathAllowed(input.filePath, input.context, input.allowExternal ?? false);
|
|
16988
16991
|
const parent = path2.dirname(candidatePath);
|
|
16989
16992
|
assertEncodingSupported(encoding);
|
|
16993
|
+
if (append) {
|
|
16994
|
+
try {
|
|
16995
|
+
const parentStat = await fs2.stat(parent);
|
|
16996
|
+
if (!parentStat.isDirectory()) {
|
|
16997
|
+
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
16998
|
+
}
|
|
16999
|
+
} catch (error45) {
|
|
17000
|
+
if (error45 instanceof Error && "code" in error45 && error45.code === "ENOENT") {
|
|
17001
|
+
if (!createDirectories) {
|
|
17002
|
+
throw createGbkError("GBK_PARENT_DIRECTORY_MISSING", `\u7236\u76EE\u5F55\u4E0D\u5B58\u5728: ${parent}`);
|
|
17003
|
+
}
|
|
17004
|
+
await fs2.mkdir(parent, { recursive: true });
|
|
17005
|
+
} else if (error45 instanceof Error && "code" in error45) {
|
|
17006
|
+
throw error45;
|
|
17007
|
+
}
|
|
17008
|
+
}
|
|
17009
|
+
let existingContent = "";
|
|
17010
|
+
let existed = false;
|
|
17011
|
+
try {
|
|
17012
|
+
const existingBuffer = await fs2.readFile(candidatePath);
|
|
17013
|
+
existingContent = import_iconv_lite.default.decode(existingBuffer, encoding);
|
|
17014
|
+
existed = true;
|
|
17015
|
+
} catch (error45) {
|
|
17016
|
+
if (!(error45 instanceof Error && "code" in error45 && error45.code === "ENOENT")) {
|
|
17017
|
+
throw createGbkError("GBK_IO_ERROR", `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25: ${candidatePath}`, error45);
|
|
17018
|
+
}
|
|
17019
|
+
}
|
|
17020
|
+
const combined = existingContent + input.content;
|
|
17021
|
+
const buffer = import_iconv_lite.default.encode(combined, encoding);
|
|
17022
|
+
await fs2.writeFile(candidatePath, buffer);
|
|
17023
|
+
return {
|
|
17024
|
+
filePath: candidatePath,
|
|
17025
|
+
encoding,
|
|
17026
|
+
bytesWritten: buffer.byteLength,
|
|
17027
|
+
created: !existed,
|
|
17028
|
+
overwritten: false,
|
|
17029
|
+
appended: true
|
|
17030
|
+
};
|
|
17031
|
+
}
|
|
16990
17032
|
try {
|
|
16991
17033
|
const stat = await fs2.stat(candidatePath);
|
|
16992
17034
|
if (stat.isDirectory()) {
|
|
@@ -17050,6 +17092,12 @@ gbk_read output looks like "3787: SENDMSG 0 content". The "3787: " is a navigati
|
|
|
17050
17092
|
oldString must be the raw file content: "SENDMSG 0 content" (no line number prefix).
|
|
17051
17093
|
Including line numbers in oldString will cause GBK_NO_MATCH even if the content exists.
|
|
17052
17094
|
|
|
17095
|
+
CRITICAL \u2014 do NOT use bare newlines or whitespace-only as oldString:
|
|
17096
|
+
Using oldString="
|
|
17097
|
+
" or oldString="" to append content will always fail.
|
|
17098
|
+
To append content to the END of a file, use gbk_write with append=true instead:
|
|
17099
|
+
gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
|
|
17100
|
+
|
|
17053
17101
|
Recommended workflow for large files (when gbk_read returned truncated=true):
|
|
17054
17102
|
1. gbk_search(pattern) \u2192 find exact lineNumber
|
|
17055
17103
|
2. gbk_read(offset=<lineNumber>, limit=20) \u2192 get the exact block (strip "N: " prefixes)
|
|
@@ -17138,13 +17186,24 @@ Workflow for large files:
|
|
|
17138
17186
|
|
|
17139
17187
|
// src/tools/gbk_write.ts
|
|
17140
17188
|
var gbk_write_default = tool({
|
|
17141
|
-
description:
|
|
17189
|
+
description: `Write GBK encoded text files.
|
|
17190
|
+
|
|
17191
|
+
**append=true** (recommended for adding content to existing files):
|
|
17192
|
+
- Reads the existing file content and appends new content at the end.
|
|
17193
|
+
- Works whether the file exists or not (creates it if missing).
|
|
17194
|
+
- Use this whenever you want to add lines/content to an existing GBK file.
|
|
17195
|
+
- Example: gbk_write(filePath=..., content="\\r\\n\u65B0\u5185\u5BB9", append=true)
|
|
17196
|
+
|
|
17197
|
+
**overwrite=true**: Replace the entire file with new content.
|
|
17198
|
+
|
|
17199
|
+
**Default (overwrite=false, append=false)**: Fails if the file already exists.`,
|
|
17142
17200
|
args: {
|
|
17143
17201
|
filePath: tool.schema.string().describe("Target file path"),
|
|
17144
|
-
content: tool.schema.string().describe("Text content to write"),
|
|
17202
|
+
content: tool.schema.string().describe("Text content to write (or append)"),
|
|
17145
17203
|
encoding: tool.schema.enum(["gbk", "gb18030"]).optional().describe("Text encoding"),
|
|
17146
17204
|
createDirectories: tool.schema.boolean().optional().describe("Create parent directories"),
|
|
17147
|
-
overwrite: tool.schema.boolean().optional().describe("Overwrite existing file"),
|
|
17205
|
+
overwrite: tool.schema.boolean().optional().describe("Overwrite existing file (replaces entire content)"),
|
|
17206
|
+
append: tool.schema.boolean().optional().describe("Append content to existing file instead of overwriting; creates the file if it does not exist"),
|
|
17148
17207
|
allowExternal: tool.schema.boolean().optional().describe("Allow paths outside workspace root")
|
|
17149
17208
|
},
|
|
17150
17209
|
async execute(args, context) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 1,
|
|
3
3
|
"packageName": "opencode-gbk-tools",
|
|
4
|
-
"packageVersion": "0.1.
|
|
4
|
+
"packageVersion": "0.1.9",
|
|
5
5
|
"artifacts": [
|
|
6
6
|
{
|
|
7
7
|
"relativePath": "tools/gbk_edit.js",
|
|
8
8
|
"kind": "tool",
|
|
9
|
-
"expectedHash": "
|
|
9
|
+
"expectedHash": "3b8f229a82519dcca21f5b9140bffc112d781390a0c47335a968f1e853ed1dc2",
|
|
10
10
|
"hashAlgorithm": "sha256"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"relativePath": "tools/gbk_write.js",
|
|
26
26
|
"kind": "tool",
|
|
27
|
-
"expectedHash": "
|
|
27
|
+
"expectedHash": "fa69eecf0c6133ebcd670fa81983a2acb5d011128f1ebc4a99fea4f5f1c37136",
|
|
28
28
|
"hashAlgorithm": "sha256"
|
|
29
29
|
},
|
|
30
30
|
{
|