@follenfang/wowdata 0.0.1
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 +10 -0
- package/README.md +175 -0
- package/dist/wowdata-darwin-amd64 +0 -0
- package/dist/wowdata-darwin-arm64 +0 -0
- package/dist/wowdata-linux-amd64 +0 -0
- package/dist/wowdata-linux-arm64 +0 -0
- package/dist/wowdata-windows-amd64.exe +0 -0
- package/npm/bin/wowdata.js +64 -0
- package/npm/lib/fs.js +24 -0
- package/npm/lib/paths.js +32 -0
- package/npm/scripts/install.js +75 -0
- package/npm/scripts/uninstall.js +49 -0
- package/npm/test/install.test.js +32 -0
- package/npm/test/launcher.test.js +19 -0
- package/package.json +37 -0
- package/skill/wowdata/SKILL.md +39 -0
- package/skill/wowdata/VERSION +1 -0
- package/skill/wowdata/agents/openai.yaml +4 -0
- package/skill/wowdata/references/clients.yaml +13 -0
- package/skill/wowdata/references/commands.md +24 -0
- package/skill/wowdata/references/locales.yaml +16 -0
- package/skill/wowdata/references/tables.yaml +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
GNU AFFERO GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 19 November 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
|
6
|
+
|
|
7
|
+
wowdata is licensed under the GNU Affero General Public License version 3 or any later version.
|
|
8
|
+
|
|
9
|
+
Full license text:
|
|
10
|
+
https://www.gnu.org/licenses/agpl-3.0.txt
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# wowdata
|
|
2
|
+
|
|
3
|
+
`wowdata` 是一个纯 Go 的 World of Warcraft 数据 CLI,用于查询和导出 CASC、DB2、Listfile、图标、法术、副本、物品、生物、装饰物和视频数据。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @follenfang/wowdata
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
npm 会完成两件事:
|
|
12
|
+
|
|
13
|
+
- 把当前平台的 Go CLI 安装到 `~/.wowdata/bin`,并提供全局 `wowdata` 命令。
|
|
14
|
+
- 把轻量 Skill 安装到 `~/.agents/skills/wowdata`。Skill 只有说明和 Reference,不包含可执行文件。
|
|
15
|
+
|
|
16
|
+
支持 Windows amd64、Linux amd64/arm64、macOS amd64/arm64。
|
|
17
|
+
|
|
18
|
+
## 目标必须明确
|
|
19
|
+
|
|
20
|
+
CLI 没有地区、产品、Build 或语言默认值。需要游戏数据的命令必须传完整目标:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
wowdata db2 rows SpellName --id 123 \
|
|
24
|
+
--source remote \
|
|
25
|
+
--region cn \
|
|
26
|
+
--product wow \
|
|
27
|
+
--build latest \
|
|
28
|
+
--locale zhCN
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
也可以先保存完整 Profile:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
wowdata profile set retail-cn \
|
|
35
|
+
--source remote \
|
|
36
|
+
--region cn \
|
|
37
|
+
--product wow \
|
|
38
|
+
--build latest \
|
|
39
|
+
--locale zhCN
|
|
40
|
+
|
|
41
|
+
wowdata db2 rows SpellName --id 123 --profile retail-cn
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Skill 的规则不同:用户没指定时,Skill 显式传入中国区 `cn`、最新 Build `latest` 和简体中文 `zhCN`;产品没有默认值,无法从用户话语或上下文确定时,Skill 会先询问用户。
|
|
45
|
+
|
|
46
|
+
## 直接查询
|
|
47
|
+
|
|
48
|
+
普通任务直接运行对应原子命令,不需要先执行预热:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
wowdata spell info --spell-id 123 --profile retail-cn
|
|
52
|
+
wowdata file lookup --file-data-id 134400 --profile retail-cn
|
|
53
|
+
wowdata icon export --file-data-id 134400 --format png --output output/icon.png --profile retail-cn
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
CLI 会在同一次命令中:
|
|
57
|
+
|
|
58
|
+
1. 解析明确的产品和 Build。
|
|
59
|
+
2. 检查远端内容身份与本地 SHA-256。
|
|
60
|
+
3. 复用有效缓存,下载缺失或过期内容。
|
|
61
|
+
4. 完成查询并返回最终结果。
|
|
62
|
+
|
|
63
|
+
准备和下载进度写入 stderr,最终结构化结果写入 stdout JSON。多个独立 CDN 对象和清单默认使用 4 个 worker 并发下载;可以通过 `wowdata cache config --workers N` 调整。
|
|
64
|
+
|
|
65
|
+
## 发现与提前准备
|
|
66
|
+
|
|
67
|
+
查看某个区域当前提供的真实产品和 Build 组合:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
wowdata casc products --source remote --region cn
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
需要提前下载时再显式使用 `warmup`:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
wowdata warmup \
|
|
77
|
+
--source remote \
|
|
78
|
+
--region cn \
|
|
79
|
+
--product wow \
|
|
80
|
+
--build latest \
|
|
81
|
+
--locale zhCN
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 本地目录
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
~/.wowdata/
|
|
88
|
+
├── bin/ CLI 二进制
|
|
89
|
+
├── config/ 用户配置
|
|
90
|
+
├── profiles/ 完整数据目标
|
|
91
|
+
├── builds/ 不可变 Build 快照
|
|
92
|
+
├── cache/
|
|
93
|
+
│ ├── casc/
|
|
94
|
+
│ ├── dbd/
|
|
95
|
+
│ ├── listfile/
|
|
96
|
+
│ ├── tact/
|
|
97
|
+
│ └── manifests/
|
|
98
|
+
├── state/ 安装和最近状态
|
|
99
|
+
├── tmp/ 下载临时文件
|
|
100
|
+
└── locks/ 跨进程下载锁
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
缓存默认上限为 20GB。每个 Profile 的当前 Build 和上一个 Build 不参与自动清理,更老的 Build 按最久未使用顺序清理。
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
wowdata cache status
|
|
107
|
+
wowdata cache verify
|
|
108
|
+
wowdata cache prune
|
|
109
|
+
wowdata cache clear
|
|
110
|
+
wowdata cache config --max-gb 30 --workers 6
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
远端不可用时,CLI 可以使用已经通过完整性校验的离线缓存;缺失、损坏和未完成的缓存不会被使用。
|
|
114
|
+
|
|
115
|
+
## 诊断和维护
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
wowdata doctor
|
|
119
|
+
wowdata update
|
|
120
|
+
wowdata update --version 1.2.3
|
|
121
|
+
wowdata uninstall
|
|
122
|
+
wowdata uninstall --keep-data
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`doctor` 只读检查 CLI/npm、目录、Profile、缓存完整性和基础网络,不自动修改数据。
|
|
126
|
+
|
|
127
|
+
`uninstall` 默认删除 CLI、托管 Skill 和整个 `~/.wowdata`。使用 `--keep-data` 时保留配置、Profile、Build 和缓存。安装遇到非本包托管的旧 Skill 时会先备份,不会直接覆盖。
|
|
128
|
+
|
|
129
|
+
## 命令
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
warmup
|
|
133
|
+
db2 schema/rows/search/foreign-key/stream
|
|
134
|
+
spell info/auras/summons
|
|
135
|
+
encounter get
|
|
136
|
+
file lookup/search/extension/get/exists/encoding/export
|
|
137
|
+
icon export
|
|
138
|
+
casc info/products/diagnose
|
|
139
|
+
item get/models/geosets/textures
|
|
140
|
+
creature display/model
|
|
141
|
+
decor list/get
|
|
142
|
+
video demux
|
|
143
|
+
profile list/show/set/remove
|
|
144
|
+
cache status/verify/prune/clear/config
|
|
145
|
+
doctor
|
|
146
|
+
update
|
|
147
|
+
uninstall
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
运行 `wowdata --help` 或 `wowdata <command> --help` 查看完整参数。
|
|
151
|
+
|
|
152
|
+
## 从源码构建
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
go test ./... -count=1
|
|
156
|
+
go build -trimpath -o dist/wowdata ./cmd/wowdata
|
|
157
|
+
npm test
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Go 负责数据解析和命令执行;Node.js 只负责 npm 安装、启动桥接和包生命周期,所有脚本都在 `npm/` 中可审计。
|
|
161
|
+
|
|
162
|
+
## 发布
|
|
163
|
+
|
|
164
|
+
推送语义版本 tag 会触发 GitHub Actions:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git tag v1.2.3
|
|
168
|
+
git push origin v1.2.3
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
工作流会测试 Go 和 npm、构建五个平台、生成 `SHA256SUMS`、创建 GitHub Release,并发布同版本 `@follenfang/wowdata`。
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
AGPL-3.0-or-later
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { binaryName, wowdataHome } = require('../lib/paths');
|
|
7
|
+
const { uninstall } = require('../scripts/uninstall');
|
|
8
|
+
|
|
9
|
+
function npmInvocation(args, platform = process.platform, env = process.env) {
|
|
10
|
+
if (platform === 'win32') {
|
|
11
|
+
return {
|
|
12
|
+
command: env.ComSpec || 'cmd.exe',
|
|
13
|
+
args: ['/d', '/s', '/c', 'npm.cmd', ...args]
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return { command: 'npm', args };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function validUpdateVersion(version) {
|
|
20
|
+
return version === 'latest' || /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runLifecycle(command, args) {
|
|
24
|
+
const env = { ...process.env };
|
|
25
|
+
let npmArgs;
|
|
26
|
+
if (command === 'update') {
|
|
27
|
+
const index = args.indexOf('--version');
|
|
28
|
+
const version = index >= 0 && args[index + 1] ? args[index + 1] : 'latest';
|
|
29
|
+
if (!validUpdateVersion(version)) {
|
|
30
|
+
process.stdout.write(`${JSON.stringify({ ok: false, command, data: { package: '@follenfang/wowdata' }, warnings: [], error: { code: 'invalid_version', message: 'version must be latest or an exact semantic version' } }, null, 2)}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
npmArgs = ['install', '-g', `@follenfang/wowdata@${version}`];
|
|
34
|
+
process.stderr.write(`update package=@follenfang/wowdata version=${version}\n`);
|
|
35
|
+
} else {
|
|
36
|
+
const keepData = args.includes('--keep-data');
|
|
37
|
+
if (keepData) env.WOWDATA_KEEP_DATA = '1';
|
|
38
|
+
uninstall({ env, keepData });
|
|
39
|
+
npmArgs = ['uninstall', '-g', '@follenfang/wowdata'];
|
|
40
|
+
process.stderr.write(`uninstall package=@follenfang/wowdata keepData=${keepData}\n`);
|
|
41
|
+
}
|
|
42
|
+
const invocation = npmInvocation(npmArgs, process.platform, env);
|
|
43
|
+
const result = spawnSync(invocation.command, invocation.args, { env, encoding: 'utf8' });
|
|
44
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
45
|
+
const ok = result.status === 0;
|
|
46
|
+
process.stdout.write(`${JSON.stringify({ ok, command, data: { package: '@follenfang/wowdata', keepData: command === 'uninstall' ? args.includes('--keep-data') : undefined, output: (result.stdout || '').trim() }, warnings: [], ...(ok ? {} : { error: { code: `npm_${command}_failed`, message: result.error?.message || `npm exited ${result.status}` } }) }, null, 2)}\n`);
|
|
47
|
+
process.exit(result.status ?? 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function main(args) {
|
|
51
|
+
if (args[0] === 'update' || args[0] === 'uninstall') runLifecycle(args[0], args.slice(1));
|
|
52
|
+
|
|
53
|
+
const binary = path.join(wowdataHome(), 'bin', binaryName());
|
|
54
|
+
const result = spawnSync(binary, args, { stdio: 'inherit' });
|
|
55
|
+
if (result.error) {
|
|
56
|
+
process.stderr.write(`wowdata launch failed: ${result.error.message}; run npm install -g @follenfang/wowdata\n`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
process.exit(result.status ?? 1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (require.main === module) main(process.argv.slice(2));
|
|
63
|
+
|
|
64
|
+
module.exports = { npmInvocation, validUpdateVersion };
|
package/npm/lib/fs.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
function ensureManagedChild(root, target) {
|
|
5
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
6
|
+
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
7
|
+
throw new Error(`path is outside managed root: ${target}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function replacePath(source, target) {
|
|
12
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
13
|
+
fs.renameSync(source, target);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function copyDirectory(source, target) {
|
|
17
|
+
fs.cpSync(source, target, { recursive: true, force: false, errorOnExist: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function timestamp() {
|
|
21
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { copyDirectory, ensureManagedChild, replacePath, timestamp };
|
package/npm/lib/paths.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const os = require('node:os');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const platformAssets = {
|
|
5
|
+
'win32-x64': 'wowdata-windows-amd64.exe',
|
|
6
|
+
'linux-x64': 'wowdata-linux-amd64',
|
|
7
|
+
'linux-arm64': 'wowdata-linux-arm64',
|
|
8
|
+
'darwin-x64': 'wowdata-darwin-amd64',
|
|
9
|
+
'darwin-arm64': 'wowdata-darwin-arm64'
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function wowdataHome(env = process.env) {
|
|
13
|
+
return path.resolve(env.WOWDATA_HOME || path.join(os.homedir(), '.wowdata'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function agentsHome(env = process.env) {
|
|
17
|
+
return path.resolve(env.AGENTS_HOME || path.join(os.homedir(), '.agents'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function assetName(platform = process.platform, arch = process.arch) {
|
|
21
|
+
const name = platformAssets[`${platform}-${arch}`];
|
|
22
|
+
if (!name) {
|
|
23
|
+
throw new Error(`unsupported platform: ${platform}-${arch}`);
|
|
24
|
+
}
|
|
25
|
+
return name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function binaryName(platform = process.platform) {
|
|
29
|
+
return platform === 'win32' ? 'wowdata.exe' : 'wowdata';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { agentsHome, assetName, binaryName, platformAssets, wowdataHome };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const crypto = require('node:crypto');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { agentsHome, assetName, binaryName, wowdataHome } = require('../lib/paths');
|
|
8
|
+
const { copyDirectory, ensureManagedChild, replacePath, timestamp } = require('../lib/fs');
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(__dirname, '..', '..');
|
|
11
|
+
const packageJSON = require(path.join(packageRoot, 'package.json'));
|
|
12
|
+
|
|
13
|
+
function install(options = {}) {
|
|
14
|
+
const env = options.env || process.env;
|
|
15
|
+
const home = wowdataHome(env);
|
|
16
|
+
const agents = agentsHome(env);
|
|
17
|
+
const dirs = ['bin', 'config', 'profiles', 'builds', 'cache/casc', 'cache/dbd', 'cache/listfile', 'cache/tact', 'cache/manifests', 'state', 'tmp', 'locks'];
|
|
18
|
+
for (const dir of dirs) fs.mkdirSync(path.join(home, dir), { recursive: true });
|
|
19
|
+
|
|
20
|
+
const sourceBinary = options.sourceBinary || path.join(packageRoot, 'dist', assetName(options.platform, options.arch));
|
|
21
|
+
if (!fs.existsSync(sourceBinary)) throw new Error(`binary asset is missing: ${sourceBinary}`);
|
|
22
|
+
const targetBinary = path.join(home, 'bin', binaryName(options.platform));
|
|
23
|
+
const binaryTemp = path.join(home, 'tmp', `${binaryName(options.platform)}.${process.pid}.tmp`);
|
|
24
|
+
fs.copyFileSync(sourceBinary, binaryTemp);
|
|
25
|
+
fs.chmodSync(binaryTemp, 0o755);
|
|
26
|
+
replacePath(binaryTemp, targetBinary);
|
|
27
|
+
|
|
28
|
+
const sourceSkill = path.join(packageRoot, 'skill', 'wowdata');
|
|
29
|
+
const skillsRoot = path.join(agents, 'skills');
|
|
30
|
+
const targetSkill = path.join(skillsRoot, 'wowdata');
|
|
31
|
+
fs.mkdirSync(skillsRoot, { recursive: true });
|
|
32
|
+
ensureManagedChild(skillsRoot, targetSkill);
|
|
33
|
+
if (fs.existsSync(targetSkill)) {
|
|
34
|
+
const markerPath = path.join(targetSkill, '.wowdata-managed.json');
|
|
35
|
+
let managed = false;
|
|
36
|
+
try {
|
|
37
|
+
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
38
|
+
managed = marker.package === '@follenfang/wowdata';
|
|
39
|
+
} catch {}
|
|
40
|
+
if (!managed) {
|
|
41
|
+
let backup = path.join(skillsRoot, `wowdata.backup-${timestamp()}`);
|
|
42
|
+
let suffix = 1;
|
|
43
|
+
while (fs.existsSync(backup)) backup = path.join(skillsRoot, `wowdata.backup-${timestamp()}-${suffix++}`);
|
|
44
|
+
fs.renameSync(targetSkill, backup);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const skillTemp = path.join(skillsRoot, `.wowdata.${process.pid}.tmp`);
|
|
48
|
+
fs.rmSync(skillTemp, { recursive: true, force: true });
|
|
49
|
+
copyDirectory(sourceSkill, skillTemp);
|
|
50
|
+
const marker = {
|
|
51
|
+
schema: 'wowdata.skill-install.v1',
|
|
52
|
+
package: '@follenfang/wowdata',
|
|
53
|
+
version: packageJSON.version,
|
|
54
|
+
installedAt: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
fs.writeFileSync(path.join(skillTemp, '.wowdata-managed.json'), `${JSON.stringify(marker, null, 2)}\n`);
|
|
57
|
+
replacePath(skillTemp, targetSkill);
|
|
58
|
+
|
|
59
|
+
const sha256 = crypto.createHash('sha256').update(fs.readFileSync(targetBinary)).digest('hex');
|
|
60
|
+
const state = { schema: 'wowdata.install.v1', package: '@follenfang/wowdata', version: packageJSON.version, binary: targetBinary, sha256 };
|
|
61
|
+
fs.writeFileSync(path.join(home, 'state', 'install.json'), `${JSON.stringify(state, null, 2)}\n`);
|
|
62
|
+
return { home, targetBinary, targetSkill, sha256 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (require.main === module) {
|
|
66
|
+
try {
|
|
67
|
+
const result = install();
|
|
68
|
+
process.stderr.write(`wowdata installed binary=${result.targetBinary} skill=${result.targetSkill}\n`);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
process.stderr.write(`wowdata install failed: ${error.message}\n`);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { install };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { agentsHome, wowdataHome } = require('../lib/paths');
|
|
7
|
+
const { ensureManagedChild } = require('../lib/fs');
|
|
8
|
+
|
|
9
|
+
function uninstall(options = {}) {
|
|
10
|
+
const env = options.env || process.env;
|
|
11
|
+
const home = wowdataHome(env);
|
|
12
|
+
const skillsRoot = path.join(agentsHome(env), 'skills');
|
|
13
|
+
const targetSkill = path.join(skillsRoot, 'wowdata');
|
|
14
|
+
ensureManagedChild(skillsRoot, targetSkill);
|
|
15
|
+
let skillRemoved = false;
|
|
16
|
+
if (fs.existsSync(targetSkill)) {
|
|
17
|
+
try {
|
|
18
|
+
const marker = JSON.parse(fs.readFileSync(path.join(targetSkill, '.wowdata-managed.json'), 'utf8'));
|
|
19
|
+
if (marker.package === '@follenfang/wowdata') {
|
|
20
|
+
fs.rmSync(targetSkill, { recursive: true, force: true });
|
|
21
|
+
skillRemoved = true;
|
|
22
|
+
}
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const keepData = options.keepData ?? env.WOWDATA_KEEP_DATA === '1';
|
|
27
|
+
if (keepData) {
|
|
28
|
+
const bin = path.join(home, 'bin');
|
|
29
|
+
ensureManagedChild(home, bin);
|
|
30
|
+
fs.rmSync(bin, { recursive: true, force: true });
|
|
31
|
+
} else if (fs.existsSync(home)) {
|
|
32
|
+
const parent = path.dirname(home);
|
|
33
|
+
ensureManagedChild(parent, home);
|
|
34
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
return { home, keepData, skillRemoved };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (require.main === module) {
|
|
40
|
+
try {
|
|
41
|
+
const result = uninstall();
|
|
42
|
+
process.stderr.write(`wowdata uninstalled keepData=${result.keepData} skillRemoved=${result.skillRemoved}\n`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
process.stderr.write(`wowdata uninstall failed: ${error.message}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { uninstall };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const test = require('node:test');
|
|
7
|
+
const assert = require('node:assert/strict');
|
|
8
|
+
const { install } = require('../scripts/install');
|
|
9
|
+
const { uninstall } = require('../scripts/uninstall');
|
|
10
|
+
|
|
11
|
+
test('installs binary and Skill, backs up unknown Skill, and uninstalls data', () => {
|
|
12
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'wowdata-npm-'));
|
|
13
|
+
const home = path.join(root, '.wowdata');
|
|
14
|
+
const agents = path.join(root, '.agents');
|
|
15
|
+
const oldSkill = path.join(agents, 'skills', 'wowdata');
|
|
16
|
+
fs.mkdirSync(oldSkill, { recursive: true });
|
|
17
|
+
fs.writeFileSync(path.join(oldSkill, 'user.txt'), 'keep');
|
|
18
|
+
const sourceBinary = path.join(root, process.platform === 'win32' ? 'source.exe' : 'source');
|
|
19
|
+
fs.writeFileSync(sourceBinary, 'binary');
|
|
20
|
+
|
|
21
|
+
const result = install({ env: { WOWDATA_HOME: home, AGENTS_HOME: agents }, sourceBinary, platform: process.platform, arch: process.arch });
|
|
22
|
+
assert.equal(fs.existsSync(result.targetBinary), true);
|
|
23
|
+
assert.equal(fs.existsSync(path.join(result.targetSkill, 'SKILL.md')), true);
|
|
24
|
+
const backups = fs.readdirSync(path.join(agents, 'skills')).filter((name) => name.startsWith('wowdata.backup-'));
|
|
25
|
+
assert.equal(backups.length, 1);
|
|
26
|
+
assert.equal(fs.readFileSync(path.join(agents, 'skills', backups[0], 'user.txt'), 'utf8'), 'keep');
|
|
27
|
+
|
|
28
|
+
uninstall({ env: { WOWDATA_HOME: home, AGENTS_HOME: agents }, keepData: false });
|
|
29
|
+
assert.equal(fs.existsSync(home), false);
|
|
30
|
+
assert.equal(fs.existsSync(result.targetSkill), false);
|
|
31
|
+
assert.equal(fs.existsSync(path.join(agents, 'skills', backups[0], 'user.txt')), true);
|
|
32
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const { npmInvocation, validUpdateVersion } = require('../bin/wowdata');
|
|
6
|
+
|
|
7
|
+
test('uses cmd.exe to launch npm.cmd on Windows', () => {
|
|
8
|
+
const invocation = npmInvocation(['uninstall', '-g', '@follenfang/wowdata'], 'win32', { ComSpec: 'C:\\Windows\\System32\\cmd.exe' });
|
|
9
|
+
assert.equal(invocation.command, 'C:\\Windows\\System32\\cmd.exe');
|
|
10
|
+
assert.deepEqual(invocation.args, ['/d', '/s', '/c', 'npm.cmd', 'uninstall', '-g', '@follenfang/wowdata']);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('accepts only latest or exact semantic versions', () => {
|
|
14
|
+
assert.equal(validUpdateVersion('latest'), true);
|
|
15
|
+
assert.equal(validUpdateVersion('1.2.3'), true);
|
|
16
|
+
assert.equal(validUpdateVersion('1.2.3-beta.1'), true);
|
|
17
|
+
assert.equal(validUpdateVersion('latest & whoami'), false);
|
|
18
|
+
assert.equal(validUpdateVersion('^1.2.3'), false);
|
|
19
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@follenfang/wowdata",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Auditable CLI for querying World of Warcraft CASC and DB2 data",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"wowdata": "npm/bin/wowdata.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"npm/",
|
|
13
|
+
"skill/wowdata/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node npm/scripts/install.js",
|
|
19
|
+
"preuninstall": "node npm/scripts/uninstall.js",
|
|
20
|
+
"test": "node --test npm/test/*.test.js",
|
|
21
|
+
"pack:check": "npm pack --dry-run"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/Follen/wowdata.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/Follen/wowdata/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/Follen/wowdata#readme"
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wowdata
|
|
3
|
+
description: Use the wowdata CLI for World of Warcraft CASC, DB2, file, icon, spell, encounter, item, creature, decor, video, Build, cache, and diagnostic tasks.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# wowdata
|
|
7
|
+
|
|
8
|
+
CLI only. Run `wowdata` from PATH. Do not look for a bundled executable and do not use any server transport.
|
|
9
|
+
|
|
10
|
+
## Target Contract
|
|
11
|
+
|
|
12
|
+
Every data command must receive a complete target or an explicit `--profile`.
|
|
13
|
+
|
|
14
|
+
1. Preserve source, path, region, product, Build, and locale when the user states them.
|
|
15
|
+
2. For an unspecified remote region, Build, or locale, explicitly pass `--source remote --region cn --build latest --locale zhCN`.
|
|
16
|
+
3. Product has no default. Resolve it from the user's words or the current conversation only when there is one unambiguous product. Otherwise ask the user which product they mean before running the CLI.
|
|
17
|
+
4. Never rely on CLI defaults. The CLI intentionally has none.
|
|
18
|
+
|
|
19
|
+
Read [clients.yaml](references/clients.yaml) for stable product aliases and [locales.yaml](references/locales.yaml) for language aliases. Do not store a current Build number in this Skill.
|
|
20
|
+
|
|
21
|
+
## Atomic Use
|
|
22
|
+
|
|
23
|
+
Call the business command directly. The CLI checks Build identity and cache integrity, downloads missing data, reports preparation on stderr, then returns the final JSON on stdout. Do not run `warmup` before ordinary queries.
|
|
24
|
+
|
|
25
|
+
Use `casc products` only when the user asks which combinations exist. Use `warmup` only when the user explicitly asks to download data ahead of time. Use `doctor` when a command reports an environment, cache, target, or network problem.
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
wowdata db2 rows SpellName --id 123 --source remote --region cn --product wow --build latest --locale zhCN
|
|
29
|
+
wowdata spell info --spell-id 123 --source remote --region cn --product wow_classic --build latest --locale zhCN
|
|
30
|
+
wowdata icon export --file-data-id 134400 --format png --output output/icon.png --source remote --region cn --product wow --build latest --locale zhCN
|
|
31
|
+
wowdata casc products --source remote --region cn
|
|
32
|
+
wowdata doctor
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Read [commands.md](references/commands.md) before composing unfamiliar commands and [tables.yaml](references/tables.yaml) when mapping a semantic task to DB2 tables. Keep exported files in the user's requested directory or the current workspace.
|
|
36
|
+
|
|
37
|
+
## Output
|
|
38
|
+
|
|
39
|
+
Preparation and download progress is stderr. The final structured result is stdout JSON. Preserve product, resolved Build, region, locale, table, fileDataID, output path, and diagnostics in the answer so the result stays traceable.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.1
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: wowdata
|
|
3
|
+
short_description: Query and export World of Warcraft data through the wowdata CLI.
|
|
4
|
+
default_prompt: Use wowdata atomic CLI commands with a complete explicit target. Default remote region/build/locale to cn/latest/zhCN in the Skill, and ask when product is unclear.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
schema: wowdata.clients.v1
|
|
2
|
+
clients:
|
|
3
|
+
retail: wow
|
|
4
|
+
ptr: wowt
|
|
5
|
+
ptr2: wowxptr
|
|
6
|
+
beta: wow_beta
|
|
7
|
+
classic: wow_classic
|
|
8
|
+
classic-ptr: wow_classic_ptr
|
|
9
|
+
classic-beta: wow_classic_beta
|
|
10
|
+
classic-era: wow_classic_era
|
|
11
|
+
classic-era-ptr: wow_classic_era_ptr
|
|
12
|
+
classic-anniversary: wow_anniversary
|
|
13
|
+
classic-titan: wow_classic_titan
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Command Reference
|
|
2
|
+
|
|
3
|
+
Run `wowdata <command> --help` when a flag is uncertain. Every data command needs either `--profile <name>` or a complete target.
|
|
4
|
+
|
|
5
|
+
| Intent | Atomic command |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Discover products and Builds | `casc products` |
|
|
8
|
+
| Prepare data ahead of time | `warmup` |
|
|
9
|
+
| DB2 schema, rows, text search, relations, stream | `db2 schema/rows/search/foreign-key/stream` |
|
|
10
|
+
| Spell details, auras, summons | `spell info/auras/summons` |
|
|
11
|
+
| Encounter tree | `encounter get` |
|
|
12
|
+
| File lookup, search, type, fetch, existence, encoding, export | `file lookup/search/extension/get/exists/encoding/export` |
|
|
13
|
+
| Export BLP icon | `icon export` |
|
|
14
|
+
| Build and CASC state | `casc info/products/diagnose` |
|
|
15
|
+
| Item data and assets | `item get/models/geosets/textures` |
|
|
16
|
+
| Creature display and model | `creature display/model` |
|
|
17
|
+
| Decor data | `decor list/get` |
|
|
18
|
+
| Video container | `video demux` |
|
|
19
|
+
| Named complete targets | `profile list/show/set/remove` |
|
|
20
|
+
| Cache lifecycle | `cache status/verify/prune/clear/config` |
|
|
21
|
+
| Installation diagnosis | `doctor` |
|
|
22
|
+
| Package maintenance | `update`, `uninstall` |
|
|
23
|
+
|
|
24
|
+
Do not use the obsolete `query rows` spelling. The command is `db2 rows`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
schema: wowdata.locales.v1
|
|
2
|
+
default: zhCN
|
|
3
|
+
locales:
|
|
4
|
+
zhCN: [简体中文, 国服中文, cn]
|
|
5
|
+
zhTW: [繁体中文, 台湾中文, tw]
|
|
6
|
+
enUS: [英文, 美式英文, us]
|
|
7
|
+
enGB: [英式英文, gb]
|
|
8
|
+
koKR: [韩文, kr]
|
|
9
|
+
frFR: [法文, fr]
|
|
10
|
+
deDE: [德文, de]
|
|
11
|
+
esES: [西班牙文, es]
|
|
12
|
+
esMX: [拉美西语, mx]
|
|
13
|
+
ruRU: [俄文, ru]
|
|
14
|
+
ptBR: [巴西葡语, br]
|
|
15
|
+
itIT: [意大利文, it]
|
|
16
|
+
ptPT: [葡萄牙文, pt]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
schema: wowdata.tables.v1
|
|
2
|
+
intents:
|
|
3
|
+
spell: [SpellName, Spell, SpellEffect, SpellMisc, SpellCastTimes, SpellDuration, SpellRange]
|
|
4
|
+
encounter: [JournalEncounterSection]
|
|
5
|
+
item: [Item, ItemSparse, ModelFileData, TextureFileData, ComponentModelFileData, ItemDisplayInfo, HelmetGeosetData, ItemDisplayInfoMaterialRes, ItemModifiedAppearance, ItemAppearance]
|
|
6
|
+
creature: [CreatureDisplayInfo, CreatureModelData, CreatureDisplayInfoGeosetData]
|
|
7
|
+
decor: [HouseDecor]
|