opencode-gbk-tools 0.1.8 → 0.1.10
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 +111 -102
- package/dist/cli/index.js +3 -3
- package/dist/opencode-tools/gbk_edit.js +14 -1
- package/dist/opencode-tools/gbk_read.js +13 -0
- package/dist/opencode-tools/gbk_search.js +9 -0
- package/dist/opencode-tools/gbk_write.js +12 -0
- package/dist/opencode-tools/text_edit.js +16816 -0
- package/dist/opencode-tools/text_read.js +16652 -0
- package/dist/opencode-tools/text_write.js +16662 -0
- package/dist/plugin/index.js +747 -28
- package/dist/plugins/opencode-gbk-tools.js +28 -0
- package/dist/release-manifest.json +29 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,158 +1,167 @@
|
|
|
1
1
|
# opencode-gbk-tools
|
|
2
2
|
|
|
3
|
-
为 OpenCode
|
|
3
|
+
为 OpenCode 提供一套自动识别编码的文本工具,以及面向 `GBK` / `GB18030` 的专用工具。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
解决 OpenCode 内置工具难以稳定处理非 UTF-8 文本文件的问题,并让所有 agents 默认优先使用可保留原编码的 `text_*` 工具。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- `gbk_write`
|
|
9
|
-
- `gbk_edit`
|
|
10
|
-
- `gbk-engine` agent
|
|
7
|
+
---
|
|
11
8
|
|
|
12
|
-
##
|
|
9
|
+
## 提供的工具
|
|
10
|
+
|
|
11
|
+
| 工具 | 用途 |
|
|
12
|
+
|------|------|
|
|
13
|
+
| `text_read` | 自动识别文件编码并按行读取,优先用于通用文本文件 |
|
|
14
|
+
| `text_write` | 自动保持已有文件编码、BOM 和换行风格后写入 |
|
|
15
|
+
| `text_edit` | 自动保持已有文件编码、BOM 和换行风格后编辑 |
|
|
16
|
+
| `gbk_read` | 读取 GBK/GB18030 文件,支持分页、尾部预览 |
|
|
17
|
+
| `gbk_write` | 写入或追加内容到 GBK 文件(`append=true` 支持追加) |
|
|
18
|
+
| `gbk_edit` | 精确替换 GBK 文件中的指定文本块 |
|
|
19
|
+
| `gbk_search` | 在 GBK 文件中搜索关键词,返回行号和上下文 |
|
|
20
|
+
| 本地/全局 plugin 规则 | 给所有 agents 注入“优先使用 `text_*`”的系统提示 |
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
---
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
## 安装
|
|
17
25
|
|
|
18
26
|
```bash
|
|
19
|
-
npx opencode-gbk-tools
|
|
27
|
+
npx opencode-gbk-tools install
|
|
20
28
|
```
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
安装完成后**重启 OpenCode**,工具立即生效。
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
- 自动加入 `plugin: ["opencode-gbk-tools"]`
|
|
26
|
-
- 安装 `.opencode/agents/gbk-engine.md`
|
|
32
|
+
---
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
## 卸载
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
npx opencode-gbk-tools setup
|
|
32
|
-
```
|
|
36
|
+
### 一键卸载(对应一键安装)
|
|
33
37
|
|
|
34
|
-
`opencode
|
|
38
|
+
如果当初是用 `npx opencode-gbk-tools install` 全局安装的,执行:
|
|
35
39
|
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
"$schema": "https://opencode.ai/config.json",
|
|
39
|
-
"plugin": ["opencode-gbk-tools"]
|
|
40
|
-
}
|
|
40
|
+
```bash
|
|
41
|
+
npx opencode-gbk-tools uninstall
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
---
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
- `gbk_write`
|
|
47
|
-
- `gbk_edit`
|
|
46
|
+
## 工具使用说明
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
### text_read / text_write / text_edit
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
- 默认 `encoding=auto`
|
|
51
|
+
- 已有文件会尽量保持:
|
|
52
|
+
- 原编码
|
|
53
|
+
- BOM
|
|
54
|
+
- 换行风格
|
|
55
|
+
- 当前支持:
|
|
56
|
+
- `utf8`
|
|
57
|
+
- `utf8-bom`
|
|
58
|
+
- `utf16le`
|
|
59
|
+
- `utf16be`
|
|
60
|
+
- `gbk`
|
|
61
|
+
- `gb18030`
|
|
52
62
|
|
|
53
|
-
|
|
63
|
+
优先使用建议:
|
|
54
64
|
|
|
55
|
-
```
|
|
56
|
-
|
|
65
|
+
```text
|
|
66
|
+
通用文本文件:text_read / text_write / text_edit
|
|
67
|
+
明确 GBK 文件:gbk_read / gbk_write / gbk_edit / gbk_search
|
|
57
68
|
```
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
### gbk_read — 读取文件
|
|
60
71
|
|
|
61
|
-
```bash
|
|
62
|
-
npm install -g opencode-gbk-tools
|
|
63
|
-
opencode-gbk install
|
|
64
|
-
opencode-gbk setup
|
|
65
72
|
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
opencode-gbk install --project
|
|
73
|
+
gbk_read(filePath="文件路径")
|
|
74
|
+
gbk_read(filePath="文件路径", offset=100, limit=50) # 从第100行读取50行
|
|
75
|
+
gbk_read(filePath="文件路径", tail=true, limit=30) # 读取最后30行
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
- 全局安装目标:`~/.config/opencode`
|
|
76
|
-
- 项目安装目标:当前命令执行目录下的 `.opencode`
|
|
78
|
+
- 返回带行号的内容,格式为 `行号: 内容`
|
|
79
|
+
- `limit` 建议不超过 500 行,大文件请先用 `gbk_search` 定位
|
|
77
80
|
|
|
78
|
-
|
|
81
|
+
### gbk_search — 搜索内容
|
|
79
82
|
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
```
|
|
84
|
+
gbk_search(filePath="文件路径", pattern="搜索关键词")
|
|
85
|
+
gbk_search(filePath="文件路径", pattern="[@标签名]", contextLines=5)
|
|
86
|
+
```
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
opencode-gbk install --project
|
|
86
|
-
opencode-gbk install --force
|
|
88
|
+
- 返回匹配行号及上下文,是处理大文件的第一步
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
opencode-gbk uninstall --project
|
|
90
|
+
### gbk_edit — 编辑文件
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
```
|
|
93
|
+
gbk_edit(filePath="文件路径", oldString="原文内容", newString="新内容")
|
|
94
|
+
gbk_edit(filePath="文件路径", oldString="原文", newString="新文", startLine=100, endLine=200)
|
|
93
95
|
```
|
|
94
96
|
|
|
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`
|
|
97
|
+
**重要:`oldString` 必须是文件的原始内容,不能包含 `gbk_read` 输出的行号前缀。**
|
|
103
98
|
|
|
104
|
-
|
|
99
|
+
错误示范(包含行号前缀,会失败):
|
|
100
|
+
```
|
|
101
|
+
oldString="3787: SENDMSG 0 内容"
|
|
102
|
+
```
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
正确示范(纯文件内容):
|
|
105
|
+
```
|
|
106
|
+
oldString="SENDMSG 0 内容"
|
|
109
107
|
```
|
|
110
108
|
|
|
111
|
-
|
|
109
|
+
### gbk_write — 写入文件
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
```
|
|
112
|
+
gbk_write(filePath="文件路径", content="内容", overwrite=true) # 覆盖写入
|
|
113
|
+
gbk_write(filePath="文件路径", content="\r\n新增内容", append=true) # 追加到末尾
|
|
114
|
+
```
|
|
116
115
|
|
|
117
|
-
|
|
116
|
+
- `append=true`:在文件末尾追加内容,文件不存在时自动创建
|
|
117
|
+
- `overwrite=true`:覆盖整个文件
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
---
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
## 大文件操作建议
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
- `setup` 会自动写入 `plugin` 配置并安装 `gbk-engine`
|
|
125
|
-
- `gbk-engine` 仍通过 CLI 安装到 `.opencode/agents/` 或 `~/.config/opencode/agents/`
|
|
126
|
-
- 如果你只需要工具,不需要 agent,只配置 `plugin` 即可
|
|
123
|
+
对于行数超过 500 行的大文件,推荐以下工作流:
|
|
127
124
|
|
|
128
|
-
|
|
125
|
+
```
|
|
126
|
+
1. gbk_search(pattern="要找的内容") → 获取行号
|
|
127
|
+
2. gbk_read(offset=行号-5, limit=20) → 读取目标区域
|
|
128
|
+
3. gbk_edit(oldString=..., newString=..., startLine=..., endLine=...) → 精确编辑
|
|
129
|
+
```
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
- 文件内容写入必须走 `gbk_write`
|
|
132
|
-
- 文件内容修改必须走 `gbk_edit`
|
|
133
|
-
- 内置 `read`、`edit`、`grep` 被限制
|
|
131
|
+
---
|
|
134
132
|
|
|
135
133
|
## 已知限制
|
|
136
134
|
|
|
137
135
|
- 只支持文本文件,不支持二进制文件
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
- `edit: deny` 在 OpenCode 中会一起限制内置 `write`、`patch`、`multiedit`
|
|
136
|
+
- 自动识别目前只覆盖 `utf8`、`utf8-bom`、`utf16le`、`utf16be`、`gbk`、`gb18030`
|
|
137
|
+
- 编码检测在歧义场景下可能返回 `TEXT_UNKNOWN_ENCODING`,此时应显式指定 `encoding`
|
|
141
138
|
- 对无法映射的字符沿用 `iconv-lite` 默认替代行为
|
|
142
139
|
|
|
143
|
-
|
|
140
|
+
---
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
- `gbk_read` 返回 `fileSize`、`newlineStyle`、`streamed`、`truncated`
|
|
147
|
-
- `gbk_edit` 支持 `startLine/endLine`
|
|
148
|
-
- `gbk_edit` 支持 `startAnchor/endAnchor`
|
|
149
|
-
- 大文件会自动走更省内存的流式路径
|
|
142
|
+
## 常见问题
|
|
150
143
|
|
|
151
|
-
|
|
144
|
+
**Q:安装后 OpenCode 里看不到工具?**
|
|
145
|
+
A:重启 OpenCode。工具在 OpenCode 启动时加载,安装后需要重启才生效。
|
|
152
146
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
**Q:`npx` 提示找不到命令?**
|
|
148
|
+
A:请先安装 Node.js(https://nodejs.org/),版本需要 18 或以上。
|
|
149
|
+
|
|
150
|
+
**Q:Windows 路径在哪?**
|
|
151
|
+
A:全局安装目录为 `C:\Users\你的用户名\.config\opencode\tools\`
|
|
152
|
+
|
|
153
|
+
**Q:gbk_edit 提示"未找到需要替换的文本"?**
|
|
154
|
+
A:检查 `oldString` 是否包含了行号前缀(如 `"3787: "`),去掉行号前缀后重试。若要在文件末尾追加内容,请使用 `gbk_write [append=true]`。
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 版本历史
|
|
159
|
+
|
|
160
|
+
| 版本 | 说明 |
|
|
161
|
+
|------|------|
|
|
162
|
+
| 0.1.10 | 新增 `text_read` / `text_write` / `text_edit`,自动识别并保持原编码、BOM 与换行风格;同时通过 plugin 规则让所有 agents 默认优先使用 `text_*` |
|
|
163
|
+
| 0.1.9 | `gbk_edit` 修复精确匹配路径写入 CRLF 文件时换行风格变 mixed 的 bug |
|
|
164
|
+
| 0.1.8 | `gbk_write` 新增 `append=true` 追加模式 |
|
|
165
|
+
| 0.1.7 | `gbk_edit` 自动剥离行号前缀后重试匹配 |
|
|
166
|
+
| 0.1.6 | 新增 `gbk_search` 搜索工具 |
|
|
167
|
+
| 0.1.5 | 初始版本 |
|
package/dist/cli/index.js
CHANGED
|
@@ -60,7 +60,7 @@ function resolveTargetBase(target, cwd) {
|
|
|
60
60
|
// src/cli/install.ts
|
|
61
61
|
async function installCommand(args) {
|
|
62
62
|
const targetBase = resolveTargetBase(args.target, args.cwd);
|
|
63
|
-
const allowedArtifacts = new Set(args.artifacts ?? ["tool", "agent"]);
|
|
63
|
+
const allowedArtifacts = new Set(args.artifacts ?? ["tool", "agent", "plugin"]);
|
|
64
64
|
const releaseManifest = JSON.parse(await fs3.readFile(path3.join(args.packageRoot, "dist", "release-manifest.json"), "utf8"));
|
|
65
65
|
const selectedArtifacts = releaseManifest.artifacts.filter((artifact) => allowedArtifacts.has(artifact.kind));
|
|
66
66
|
const existingManifest = await loadInstalledManifest(targetBase);
|
|
@@ -70,7 +70,7 @@ async function installCommand(args) {
|
|
|
70
70
|
throw new Error(`\u68C0\u6D4B\u5230\u672A\u53D7\u7BA1\u6587\u4EF6\u51B2\u7A81: ${targetPath}`);
|
|
71
71
|
}
|
|
72
72
|
await ensureDir(path3.dirname(targetPath));
|
|
73
|
-
const sourceRoot = artifact.kind === "tool" ? path3.join(args.packageRoot, "dist", "opencode-tools") : path3.join(args.packageRoot, "dist", "agents");
|
|
73
|
+
const sourceRoot = artifact.kind === "tool" ? path3.join(args.packageRoot, "dist", "opencode-tools") : artifact.kind === "agent" ? path3.join(args.packageRoot, "dist", "agents") : path3.join(args.packageRoot, "dist", "plugins");
|
|
74
74
|
await fs3.copyFile(path3.join(sourceRoot, path3.basename(artifact.relativePath)), targetPath);
|
|
75
75
|
}
|
|
76
76
|
const installedManifest = {
|
|
@@ -181,7 +181,7 @@ async function setupCommand(args) {
|
|
|
181
181
|
await fs6.mkdir(configBase, { recursive: true });
|
|
182
182
|
const configPath = await resolveConfigFile(configBase);
|
|
183
183
|
await ensurePluginConfigured(configPath, pluginName);
|
|
184
|
-
const installResult = await installCommand({ ...args, force: true, artifacts: ["agent"] });
|
|
184
|
+
const installResult = await installCommand({ ...args, force: true, artifacts: ["agent", "plugin"] });
|
|
185
185
|
return {
|
|
186
186
|
configPath,
|
|
187
187
|
targetBase: installResult.targetBase
|
|
@@ -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);
|
|
@@ -16777,6 +16779,17 @@ and avoid false matches. Scoped edits also improve performance on very large fil
|
|
|
16777
16779
|
},
|
|
16778
16780
|
async execute(args, context) {
|
|
16779
16781
|
const result = await replaceGbkFileText({ ...args, context });
|
|
16782
|
+
context.metadata({
|
|
16783
|
+
title: `GBK \u7F16\u8F91 ${result.encoding.toUpperCase()} x${result.replacements}`,
|
|
16784
|
+
metadata: {
|
|
16785
|
+
filePath: result.filePath,
|
|
16786
|
+
encoding: result.encoding,
|
|
16787
|
+
replacements: result.replacements,
|
|
16788
|
+
occurrencesBefore: result.occurrencesBefore,
|
|
16789
|
+
bytesRead: result.bytesRead,
|
|
16790
|
+
bytesWritten: result.bytesWritten
|
|
16791
|
+
}
|
|
16792
|
+
});
|
|
16780
16793
|
return JSON.stringify(result, null, 2);
|
|
16781
16794
|
}
|
|
16782
16795
|
});
|
|
@@ -16632,6 +16632,19 @@ Workflow when truncated=true:
|
|
|
16632
16632
|
},
|
|
16633
16633
|
async execute(args, context) {
|
|
16634
16634
|
const result = await readGbkFile({ ...args, context });
|
|
16635
|
+
const lineRange = `${result.startLine}-${result.endLine}`;
|
|
16636
|
+
context.metadata({
|
|
16637
|
+
title: `GBK \u8BFB\u53D6 ${result.encoding.toUpperCase()} ${lineRange}`,
|
|
16638
|
+
metadata: {
|
|
16639
|
+
filePath: result.filePath,
|
|
16640
|
+
encoding: result.encoding,
|
|
16641
|
+
lineRange,
|
|
16642
|
+
totalLines: result.totalLines,
|
|
16643
|
+
newlineStyle: result.newlineStyle,
|
|
16644
|
+
truncated: result.truncated,
|
|
16645
|
+
tail: result.tail
|
|
16646
|
+
}
|
|
16647
|
+
});
|
|
16635
16648
|
return JSON.stringify(result, null, 2);
|
|
16636
16649
|
}
|
|
16637
16650
|
});
|
|
@@ -16454,6 +16454,15 @@ Workflow for large files:
|
|
|
16454
16454
|
},
|
|
16455
16455
|
async execute(args, context) {
|
|
16456
16456
|
const result = await searchGbkFile({ ...args, context });
|
|
16457
|
+
context.metadata({
|
|
16458
|
+
title: `GBK \u641C\u7D22 ${result.encoding.toUpperCase()} ${result.matchCount} \u547D\u4E2D`,
|
|
16459
|
+
metadata: {
|
|
16460
|
+
filePath: result.filePath,
|
|
16461
|
+
encoding: result.encoding,
|
|
16462
|
+
totalLines: result.totalLines,
|
|
16463
|
+
matchCount: result.matchCount
|
|
16464
|
+
}
|
|
16465
|
+
});
|
|
16457
16466
|
return JSON.stringify(result, null, 2);
|
|
16458
16467
|
}
|
|
16459
16468
|
});
|
|
@@ -16432,6 +16432,18 @@ var gbk_write_default = tool({
|
|
|
16432
16432
|
},
|
|
16433
16433
|
async execute(args, context) {
|
|
16434
16434
|
const result = await writeGbkFile({ ...args, context });
|
|
16435
|
+
const action = result.appended ? "\u8FFD\u52A0" : result.overwritten ? "\u8986\u76D6" : "\u5199\u5165";
|
|
16436
|
+
context.metadata({
|
|
16437
|
+
title: `GBK ${action} ${result.encoding.toUpperCase()}`,
|
|
16438
|
+
metadata: {
|
|
16439
|
+
filePath: result.filePath,
|
|
16440
|
+
encoding: result.encoding,
|
|
16441
|
+
created: result.created,
|
|
16442
|
+
overwritten: result.overwritten,
|
|
16443
|
+
appended: result.appended ?? false,
|
|
16444
|
+
bytesWritten: result.bytesWritten
|
|
16445
|
+
}
|
|
16446
|
+
});
|
|
16435
16447
|
return JSON.stringify(result, null, 2);
|
|
16436
16448
|
}
|
|
16437
16449
|
});
|