opencode-gbk-tools 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # opencode-gbk-tools
2
+
3
+ 为 OpenCode 提供一套面向 `GBK` / `GB18030` 文本工程的自定义工具与专用 agent。
4
+
5
+ ## 功能
6
+
7
+ - `gbk_read`
8
+ - `gbk_write`
9
+ - `gbk_edit`
10
+ - `gbk-engine` agent
11
+
12
+ ## 安装
13
+
14
+ 一次性使用:
15
+
16
+ ```bash
17
+ npx opencode-gbk-tools install
18
+ ```
19
+
20
+ 全局安装后使用:
21
+
22
+ ```bash
23
+ npm install -g opencode-gbk-tools
24
+ opencode-gbk install
25
+ ```
26
+
27
+ 安装到当前项目:
28
+
29
+ ```bash
30
+ opencode-gbk install --project
31
+ ```
32
+
33
+ ## 目录
34
+
35
+ - 全局安装目标:`~/.config/opencode`
36
+ - 项目安装目标:当前命令执行目录下的 `.opencode`
37
+
38
+ ## CLI
39
+
40
+ ```bash
41
+ opencode-gbk install
42
+ opencode-gbk install --project
43
+ opencode-gbk install --force
44
+
45
+ opencode-gbk uninstall
46
+ opencode-gbk uninstall --project
47
+
48
+ opencode-gbk doctor
49
+ opencode-gbk doctor --project
50
+ ```
51
+
52
+ ## Agent
53
+
54
+ 安装后会生成 `gbk-engine`。
55
+
56
+ 它的目标是:
57
+
58
+ - 文件内容读取必须走 `gbk_read`
59
+ - 文件内容写入必须走 `gbk_write`
60
+ - 文件内容修改必须走 `gbk_edit`
61
+ - 内置 `read`、`edit`、`grep` 被限制
62
+
63
+ ## 已知限制
64
+
65
+ - 只支持文本文件,不支持二进制文件
66
+ - 首版不做自动编码识别
67
+ - 只支持 `gbk` 和 `gb18030`
68
+ - `edit: deny` 在 OpenCode 中会一起限制内置 `write`、`patch`、`multiedit`
69
+ - 对无法映射的字符沿用 `iconv-lite` 默认替代行为
70
+
71
+ ## 发布
72
+
73
+ ```bash
74
+ npm run check
75
+ npm test
76
+ npm run build
77
+ npm pack --dry-run
78
+ ```
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { main } from "../dist/cli/index.js"
4
+
5
+ main(process.argv.slice(2)).catch((error) => {
6
+ const message = error instanceof Error ? error.message : String(error)
7
+ console.error(message)
8
+ process.exitCode = 1
9
+ })
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: 处理 GBK/GB18030 编码文件的专用代理,只使用 gbk_read、gbk_write、gbk_edit
3
+ mode: primary
4
+ permission:
5
+ read: deny
6
+ edit: deny
7
+ grep: deny
8
+ webfetch: ask
9
+ bash:
10
+ "*": deny
11
+ "git status*": allow
12
+ "npm view *": allow
13
+ ---
14
+
15
+ 你是一个专门处理 GBK/GB18030 文本文件的代理。
16
+
17
+ 规则:
18
+ - 读取文件时优先使用 `gbk_read`
19
+ - 创建或覆盖文件时优先使用 `gbk_write`
20
+ - 修改已有文件时优先使用 `gbk_edit`
21
+ - 文件发现可使用 `glob`,但文件内容读取必须使用 `gbk_read`
22
+ - 禁止依赖内置 `read`、`grep`、`write`、`edit`、`patch`
23
+ - `edit: deny` 同时覆盖内置 `write`、`patch`、`multiedit`
24
+ - 如果用户请求涉及 UTF-8 文件或二进制文件,先明确说明不适用
25
+ - 若目标文件编码不确定,先提醒用户确认是 `gbk` 还是 `gb18030`
@@ -0,0 +1,162 @@
1
+ // src/cli/index.ts
2
+ import path6 from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ // src/cli/install.ts
6
+ import fs3 from "fs/promises";
7
+ import path3 from "path";
8
+
9
+ // src/cli/fs.ts
10
+ import crypto from "crypto";
11
+ import fs from "fs/promises";
12
+ async function ensureDir(directory) {
13
+ await fs.mkdir(directory, { recursive: true });
14
+ }
15
+ async function computeFileHash(filePath) {
16
+ const buffer = await fs.readFile(filePath);
17
+ return crypto.createHash("sha256").update(buffer).digest("hex");
18
+ }
19
+ async function pathExists(filePath) {
20
+ try {
21
+ await fs.access(filePath);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ // src/cli/manifest.ts
29
+ import fs2 from "fs/promises";
30
+ import path from "path";
31
+ function getInstalledManifestPath(targetBase) {
32
+ return path.join(targetBase, "opencode-gbk-tools.manifest.json");
33
+ }
34
+ async function loadInstalledManifest(targetBase) {
35
+ try {
36
+ const content = await fs2.readFile(getInstalledManifestPath(targetBase), "utf8");
37
+ return JSON.parse(content);
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ async function writeInstalledManifest(targetBase, manifest) {
43
+ await fs2.mkdir(targetBase, { recursive: true });
44
+ await fs2.writeFile(getInstalledManifestPath(targetBase), JSON.stringify(manifest, null, 2), "utf8");
45
+ }
46
+
47
+ // src/cli/paths.ts
48
+ import os from "os";
49
+ import path2 from "path";
50
+ function getGlobalTargetBase() {
51
+ return path2.join(os.homedir(), ".config", "opencode");
52
+ }
53
+ function getProjectTargetBase(cwd) {
54
+ return path2.join(cwd, ".opencode");
55
+ }
56
+ function resolveTargetBase(target, cwd) {
57
+ return target === "global" ? getGlobalTargetBase() : getProjectTargetBase(cwd);
58
+ }
59
+
60
+ // src/cli/install.ts
61
+ async function installCommand(args) {
62
+ const targetBase = resolveTargetBase(args.target, args.cwd);
63
+ const releaseManifest = JSON.parse(await fs3.readFile(path3.join(args.packageRoot, "dist", "release-manifest.json"), "utf8"));
64
+ const existingManifest = await loadInstalledManifest(targetBase);
65
+ for (const artifact of releaseManifest.artifacts) {
66
+ const targetPath = path3.join(targetBase, artifact.relativePath);
67
+ if (await pathExists(targetPath) && !existingManifest && !args.force) {
68
+ throw new Error(`\u68C0\u6D4B\u5230\u672A\u53D7\u7BA1\u6587\u4EF6\u51B2\u7A81: ${targetPath}`);
69
+ }
70
+ await ensureDir(path3.dirname(targetPath));
71
+ const sourceRoot = artifact.kind === "tool" ? path3.join(args.packageRoot, "dist", "opencode-tools") : path3.join(args.packageRoot, "dist", "agents");
72
+ await fs3.copyFile(path3.join(sourceRoot, path3.basename(artifact.relativePath)), targetPath);
73
+ }
74
+ const installedManifest = {
75
+ manifestVersion: 1,
76
+ packageName: releaseManifest.packageName,
77
+ packageVersion: releaseManifest.packageVersion,
78
+ targetType: args.target,
79
+ targetBase,
80
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
81
+ files: releaseManifest.artifacts.map((artifact) => ({
82
+ ...artifact,
83
+ installedByVersion: releaseManifest.packageVersion
84
+ }))
85
+ };
86
+ await writeInstalledManifest(targetBase, installedManifest);
87
+ return { targetBase };
88
+ }
89
+
90
+ // src/cli/uninstall.ts
91
+ import fs4 from "fs/promises";
92
+ import path4 from "path";
93
+ async function uninstallCommand(args) {
94
+ const targetBase = resolveTargetBase(args.target, args.cwd);
95
+ const manifest = await loadInstalledManifest(targetBase);
96
+ if (!manifest) {
97
+ return { targetBase, removed: 0 };
98
+ }
99
+ let removed = 0;
100
+ for (const file of manifest.files) {
101
+ const targetPath = path4.join(targetBase, file.relativePath);
102
+ await fs4.rm(targetPath, { force: true });
103
+ removed += 1;
104
+ }
105
+ await fs4.rm(path4.join(targetBase, "opencode-gbk-tools.manifest.json"), { force: true });
106
+ return { targetBase, removed };
107
+ }
108
+
109
+ // src/cli/doctor.ts
110
+ import fs5 from "fs/promises";
111
+ import path5 from "path";
112
+ async function doctorCommand(args) {
113
+ const targetBase = resolveTargetBase(args.target, args.cwd);
114
+ const manifest = await loadInstalledManifest(targetBase);
115
+ if (!manifest) {
116
+ return { ok: false, issues: ["\u672A\u627E\u5230 installed manifest"] };
117
+ }
118
+ const issues = [];
119
+ for (const file of manifest.files) {
120
+ const targetPath = path5.join(targetBase, file.relativePath);
121
+ try {
122
+ const actual = await computeFileHash(targetPath);
123
+ if (actual !== file.expectedHash) {
124
+ issues.push(`\u6587\u4EF6\u5185\u5BB9\u4E0E manifest \u4E0D\u4E00\u81F4: ${file.relativePath}`);
125
+ }
126
+ } catch {
127
+ issues.push(`\u6587\u4EF6\u7F3A\u5931: ${file.relativePath}`);
128
+ }
129
+ }
130
+ await fs5.access(path5.join(args.packageRoot, "dist", "release-manifest.json"));
131
+ return { ok: issues.length === 0, issues };
132
+ }
133
+
134
+ // src/cli/index.ts
135
+ function resolvePackageRoot(env = process.env, moduleUrl = import.meta.url) {
136
+ if (env.OPENCODE_GBK_PACKAGE_ROOT) {
137
+ return env.OPENCODE_GBK_PACKAGE_ROOT;
138
+ }
139
+ const modulePath = fileURLToPath(moduleUrl);
140
+ return path6.resolve(path6.dirname(modulePath), "..", "..");
141
+ }
142
+ async function main(argv, env = process.env) {
143
+ const [command, ...rest] = argv;
144
+ const target = rest.includes("--project") ? "project" : "global";
145
+ const force = rest.includes("--force");
146
+ const packageRoot = resolvePackageRoot(env);
147
+ const cwd = process.cwd();
148
+ if (command === "install") {
149
+ return await installCommand({ packageRoot, target, cwd, force });
150
+ }
151
+ if (command === "uninstall") {
152
+ return await uninstallCommand({ target, cwd });
153
+ }
154
+ if (command === "doctor") {
155
+ return await doctorCommand({ packageRoot, target, cwd });
156
+ }
157
+ throw new Error(`\u672A\u77E5\u547D\u4EE4: ${command ?? ""}`);
158
+ }
159
+ export {
160
+ main,
161
+ resolvePackageRoot
162
+ };