@tencent-weixin/openclaw-weixin-cli 1.0.3 → 2.1.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/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # openclaw-weixin-installer
2
+
3
+ OpenClaw 微信消息通道插件的统一安装器。自动检测宿主 OpenClaw 版本,安装兼容的插件版本。
4
+
5
+ ## 快速开始
6
+
7
+ ```bash
8
+ npx -y @tencent-weixin/openclaw-weixin-cli install
9
+ ```
10
+
11
+ 安装器会自动完成以下步骤:
12
+ 1. 检测本地 `openclaw --version`
13
+ 2. 根据兼容矩阵选择合适的插件版本(dist-tag)
14
+ 3. 调用 `openclaw plugins install` 安装对应版本
15
+ 4. 引导扫码连接微信
16
+ 5. 重启 OpenClaw Gateway
17
+
18
+ **无需手动指定版本号。**
19
+
20
+ ## 兼容矩阵
21
+
22
+ | openclaw-weixin | 支持的 OpenClaw | dist-tag | 说明 |
23
+ |-----------------|-----------------------|---------------------------------------|----------------------|
24
+ | 1.0.x | >=2026.3.0 <2026.3.22 | `compat-host-gte2026.3.0-lt2026.3.22` | 兼容轨道 |
25
+ | 2.0.x | >=2026.3.22 | `latest` | 当前推荐主线 |
26
+
27
+ > 从 2.0.0 开始,插件采用独立 semver 版本号,不再对齐宿主 OpenClaw 版本号。
28
+
29
+ ## 手动安装
30
+
31
+ 如果需要手动指定版本,可以直接使用 openclaw 命令:
32
+
33
+ ```bash
34
+ # 查看当前 OpenClaw 版本
35
+ openclaw --version
36
+
37
+ # 当前推荐主线 (>=2026.3.22)
38
+ openclaw plugins install @tencent/openclaw-weixin@latest
39
+
40
+ # 兼容轨道 (<2026.3.22)
41
+ openclaw plugins install @tencent/openclaw-weixin@compat-host-gte2026.3.0-lt2026.3.22
42
+ ```
43
+
44
+ ## 运行时版本校验
45
+
46
+ 插件在启动时会自动检查宿主版本兼容性。如果版本不匹配,将立即抛出错误:
47
+
48
+ ```
49
+ [openclaw-weixin] 宿主版本不兼容!
50
+ 当前 OpenClaw 版本: 2026.3.10
51
+ 当前插件支持范围: >=2026.3.22
52
+ 请安装 openclaw-weixin@compat-host-gte2026.3.0-lt2026.3.22
53
+ 或运行: npx @tencent-weixin/openclaw-weixin-cli install (自动选择兼容版本)
54
+ ```
55
+
56
+ ## 故障排查
57
+
58
+ ### 宿主版本不兼容
59
+
60
+ **症状**:插件启动时报 `宿主版本不兼容` 错误。
61
+
62
+ **解决**:
63
+ ```bash
64
+ # 1. 确认 OpenClaw 版本
65
+ openclaw --version
66
+
67
+ # 2. 用统一安装器重新安装(自动匹配版本)
68
+ npx -y @tencent-weixin/openclaw-weixin-cli install
69
+ ```
70
+
71
+ ### 如何查看当前 openclaw 版本
72
+
73
+ ```bash
74
+ openclaw --version
75
+ ```
76
+
77
+ ## 发布策略
78
+
79
+ ### 本仓库(CLI installer)与插件仓库的关系
80
+
81
+ 本仓库发布的是 **CLI 安装器**(`openclaw-weixin-cli`),不是插件本身(`openclaw-weixin`)。
82
+
83
+ - **插件仓库**:openclaw-weixin,通过各分支发布到不同的 npm dist-tag
84
+ - **本仓库**:CLI 安装器,根据 `COMPAT_MATRIX` 自动选择正确的插件 dist-tag 来安装
85
+
86
+ ### 分支管理规则
87
+
88
+ 内网和外网通过不同分支管理,prerelease 分支从主分支拉出,合并回主分支:
89
+
90
+ | 分支 | 用途 | package.json 状态 |
91
+ |------|------|------------------|
92
+ | `master-prerelease` | 日常开发 + 内网发布 | 内网发布后保持内网包名和版本 |
93
+ | `master` | 外网发布 | 外网发布后保持外网包名和版本 |
94
+
95
+ **核心原则:**
96
+ - 日常开发和内网发布在 `-prerelease` 分支上进行
97
+ - 需要发外网时,将 prerelease 分支合入对应主分支,再在主分支上发布
98
+ - 发布后 package.json **不回退**,分支保持对应环境的最新发布状态
99
+ - prerelease 和主分支同构(共用 `publish.config.json`),合并时自然更新
100
+
101
+ ### CLI 发布命令
102
+
103
+ ```bash
104
+ # 在 prerelease 分支上发布内网
105
+ node scripts/publish.mjs internal patch
106
+
107
+ # dry run 预检
108
+ node scripts/publish.mjs internal patch --dry-run
109
+ ```
110
+
111
+ 发布脚本会:
112
+ 1. 检查工作区是否干净
113
+ 2. 将 `package.json` 的 name/version 和 `cli.mjs` 的 PLUGIN_SPEC 替换为目标环境的值
114
+ 3. 更新 `publish.config.json` 中对应目标的版本号
115
+ 4. 提交 release commit 并打 git tag
116
+ 5. npm publish
117
+ 6. push commit + tag
118
+
119
+ 发布后 package.json 保持发布态,不回退。如果 npm publish 失败,自动回滚 release commit 和 tag。
120
+
121
+ ### 内网验证后发外网
122
+
123
+ ```bash
124
+ # ① 在 prerelease 分支上完成内网发布和验证
125
+
126
+ # ② 合入主分支
127
+ git checkout master
128
+ git merge master-prerelease
129
+
130
+ # ③ 在主分支上发布外网
131
+ node scripts/publish.mjs external patch
132
+ ```
133
+
134
+ ### 兼容轨道发版流程
135
+
136
+ 当 openclaw 发布破坏兼容性的新版本时:
137
+
138
+ #### 1. 插件仓库操作(openclaw-weixin)
139
+
140
+ ```bash
141
+ # 从当前分支拉出兼容轨道
142
+ git checkout -b compat-host-gteX.Y.Z-ltA.B.C-prerelease master-prerelease
143
+ git checkout -b compat-host-gteX.Y.Z-ltA.B.C master
144
+ # 修改 compat 分支上的 publish.config.json,将 npmTag 改为轨道名
145
+
146
+ # master-prerelease 上适配新宿主,major + 1
147
+ ```
148
+
149
+ #### 2. CLI 仓库操作(本仓库)
150
+
151
+ ```bash
152
+ # ① 更新 lib/compat.mjs 的 COMPAT_MATRIX
153
+
154
+ # ② 在 prerelease 分支上发布内网验证
155
+ node scripts/publish.mjs internal minor
156
+
157
+ # ③ 验证后合入主分支发外网
158
+ git checkout master && git merge master-prerelease
159
+ node scripts/publish.mjs external minor
160
+ ```
161
+
162
+ #### 3. 验证清单
163
+
164
+ - [ ] `npx @tencent/openclaw-weixin-cli install` 在旧宿主上安装兼容轨道版本
165
+ - [ ] `npx @tencent/openclaw-weixin-cli install` 在新宿主上安装 latest 版本
166
+ - [ ] 已安装旧 tag 的用户重跑 CLI 后自动卸载旧版、安装新版
167
+ - [ ] 插件运行时 `version-check.mjs` 报错信息指引正确
168
+
169
+ ### 兼容轨道 bug 修复
170
+
171
+ ```bash
172
+ # ① 在 compat prerelease 分支上修复
173
+ git checkout compat-host-gteX.Y.Z-ltA.B.C-prerelease
174
+ # cherry-pick 或直接修复
175
+ node scripts/publish.mjs internal patch
176
+
177
+ # ② 需要发外网时合入 compat 主分支
178
+ git checkout compat-host-gteX.Y.Z-ltA.B.C
179
+ git merge compat-host-gteX.Y.Z-ltA.B.C-prerelease
180
+ node scripts/publish.mjs external patch
181
+
182
+ # ③ 评估是否需要同步到 master 和其他 compat 分支
183
+ ```
184
+
185
+ > CLI 不需要重新发布——同一 dist-tag 下的 patch 更新,用户通过 `openclaw plugins update` 即可获取。
186
+
187
+ ## 开发
188
+
189
+ ```bash
190
+ # 安装开发依赖
191
+ npm install
192
+
193
+ # 本地测试 install 命令
194
+ node cli.mjs install
195
+ ```
package/cli.mjs CHANGED
@@ -1,9 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execSync, spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import os from "node:os";
7
+ import {
8
+ COMPAT_MATRIX,
9
+ compareVersions,
10
+ findCompatEntry,
11
+ formatRange,
12
+ } from "./lib/compat.mjs";
4
13
 
5
14
  const PLUGIN_SPEC = "@tencent-weixin/openclaw-weixin";
6
15
  const CHANNEL_ID = "openclaw-weixin";
16
+ /** Only OpenClaw 2026.3.22–2026.3.23 need node_modules/openclaw symlink for jiti. */
17
+ const SYMLINK_OPENCLAW_MIN = "2026.3.22";
18
+ const SYMLINK_OPENCLAW_MAX = "2026.3.23";
19
+
20
+ const LEGACY_TAG_ALIASES = {
21
+ legacy: "compat-host-gte2026.3.0-lt2026.3.22",
22
+ };
7
23
 
8
24
  // ── helpers ──────────────────────────────────────────────────────────────────
9
25
 
@@ -35,6 +51,146 @@ function which(bin) {
35
51
  }
36
52
  }
37
53
 
54
+ // ── version detection ───────────────────────────────────────────────────────
55
+
56
+ function getOpenclawVersion() {
57
+ try {
58
+ const raw = run("openclaw --version");
59
+ const match = raw.match(/(\d+\.\d+\.\d+)/);
60
+ return match ? match[1] : null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function selectPluginTag(openclawVersion) {
67
+ const entry = findCompatEntry(openclawVersion);
68
+ if (entry) return entry;
69
+
70
+ error(`当前 OpenClaw 版本 ${openclawVersion} 不在任何已知兼容范围内`);
71
+ console.log("\n 已知兼容矩阵:");
72
+ for (const e of COMPAT_MATRIX) {
73
+ console.log(` ${e.label} → OpenClaw ${formatRange(e.openclawRange)}`);
74
+ }
75
+ console.log();
76
+ return null;
77
+ }
78
+
79
+ // ── symlink ──────────────────────────────────────────────────────────────────
80
+
81
+ /**
82
+ * Resolve the host openclaw package root from the `openclaw` binary.
83
+ * e.g. /Users/x/.nvm/versions/node/v22/lib/node_modules/openclaw
84
+ */
85
+ function resolveHostOpenclawRoot() {
86
+ const bin = which("openclaw");
87
+ if (!bin) return null;
88
+ try {
89
+ // Follow symlinks to the real binary, then go up to the package root.
90
+ const real = fs.realpathSync(bin);
91
+ // binary is at <root>/openclaw.mjs or <root>/dist/index.js etc.
92
+ // Walk up until we find a package.json with name "openclaw".
93
+ let dir = path.dirname(real);
94
+ for (let i = 0; i < 6; i++) {
95
+ try {
96
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
97
+ if (pkg.name === "openclaw") return dir;
98
+ } catch {}
99
+ const parent = path.dirname(dir);
100
+ if (parent === dir) break;
101
+ dir = parent;
102
+ }
103
+ } catch {}
104
+ return null;
105
+ }
106
+
107
+ /**
108
+ * Resolve the plugin extensions directory.
109
+ * Default: ~/.openclaw/extensions/openclaw-weixin
110
+ */
111
+ function resolvePluginExtDir() {
112
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(os.homedir(), ".openclaw");
113
+ return path.join(stateDir, "extensions", "openclaw-weixin");
114
+ }
115
+
116
+ /**
117
+ * Create a symlink from the plugin's node_modules/openclaw to the host
118
+ * openclaw package root. This lets jiti resolve openclaw/plugin-sdk/*
119
+ * without openclaw being a runtime dependency.
120
+ */
121
+ function hostVersionNeedsOpenclawSymlink(hostVersion) {
122
+ if (!hostVersion) return false;
123
+ const geMin = compareVersions(hostVersion, SYMLINK_OPENCLAW_MIN);
124
+ const leMax = compareVersions(hostVersion, SYMLINK_OPENCLAW_MAX);
125
+ return (
126
+ !Number.isNaN(geMin) &&
127
+ !Number.isNaN(leMax) &&
128
+ geMin >= 0 &&
129
+ leMax <= 0
130
+ );
131
+ }
132
+
133
+ function ensureOpenclawSymlink(hostVersion) {
134
+ if (!hostVersionNeedsOpenclawSymlink(hostVersion)) {
135
+ return;
136
+ }
137
+
138
+ const hostRoot = resolveHostOpenclawRoot();
139
+ if (!hostRoot) {
140
+ error("无法定位宿主 openclaw 包根目录,跳过 symlink 创建");
141
+ return;
142
+ }
143
+
144
+ const pluginDir = resolvePluginExtDir();
145
+ if (!fs.existsSync(pluginDir)) {
146
+ // Plugin not in extensions dir (might be a -l link install); skip.
147
+ return;
148
+ }
149
+
150
+ const nmDir = path.join(pluginDir, "node_modules");
151
+ const linkPath = path.join(nmDir, "openclaw");
152
+
153
+ // Check if already correct
154
+ try {
155
+ const existing = fs.readlinkSync(linkPath);
156
+ if (fs.realpathSync(existing) === fs.realpathSync(hostRoot)) {
157
+ return;
158
+ }
159
+ // Wrong target — remove and recreate
160
+ fs.unlinkSync(linkPath);
161
+ } catch {
162
+ // Not a symlink or doesn't exist — fine
163
+ }
164
+
165
+ fs.mkdirSync(nmDir, { recursive: true });
166
+ fs.symlinkSync(hostRoot, linkPath);
167
+ log(`已创建 symlink: node_modules/openclaw → ${hostRoot}`);
168
+ }
169
+
170
+ // ── installed plugin detection ───────────────────────────────────────────────
171
+
172
+ /**
173
+ * Read the openclaw config and return the dist-tag that was used to install
174
+ * the plugin. OpenClaw stores `plugins.installs.<id>.spec` as the raw
175
+ * install spec (e.g. "@tencent/openclaw-weixin@latest"). We extract the
176
+ * trailing tag/version after the last "@".
177
+ * Returns the tag string, or null if not installed / unreadable.
178
+ */
179
+ function getInstalledPluginTag() {
180
+ const stateDir = process.env.OPENCLAW_STATE_DIR || path.join(os.homedir(), ".openclaw");
181
+ const configPath = path.join(stateDir, "openclaw.json");
182
+ try {
183
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
184
+ const spec = config?.plugins?.installs?.[CHANNEL_ID]?.spec;
185
+ if (!spec) return null;
186
+ const atIdx = spec.lastIndexOf("@");
187
+ const raw = atIdx > 0 ? spec.slice(atIdx + 1) : "latest";
188
+ return LEGACY_TAG_ALIASES[raw] || raw;
189
+ } catch {
190
+ return null;
191
+ }
192
+ }
193
+
38
194
  // ── commands ─────────────────────────────────────────────────────────────────
39
195
 
40
196
  function install() {
@@ -47,10 +203,47 @@ function install() {
47
203
  }
48
204
  log("已找到本地安装的 openclaw");
49
205
 
50
- // 2. Install plugin via openclaw
51
- log("正在安装插件...");
206
+ // 2. Detect host version and select compatible plugin
207
+ const hostVersion = getOpenclawVersion();
208
+ if (!hostVersion) {
209
+ error("无法获取 openclaw 版本号,请确认 `openclaw --version` 正常工作");
210
+ process.exit(1);
211
+ }
212
+ log(`检测到 OpenClaw 版本: ${hostVersion}`);
213
+
214
+ const compat = selectPluginTag(hostVersion);
215
+ if (!compat) {
216
+ process.exit(1);
217
+ }
218
+
219
+ const pluginInstallSpec = `${PLUGIN_SPEC}@${compat.distTag}`;
220
+ log(`匹配兼容版本: ${compat.label} (dist-tag: ${compat.distTag})`);
221
+
222
+ // 3. If already installed with a different dist-tag, uninstall first
223
+ // so the fresh install picks up the correct dist-tag.
224
+ // If the installed spec is a pinned version number, skip entirely.
225
+ const installedTag = getInstalledPluginTag();
226
+ if (installedTag !== null && /^\d+\.\d+\.\d+/.test(installedTag)) {
227
+ log(`本地已安装插件为固定版本 ${installedTag},跳过升级`);
228
+ return;
229
+ }
230
+ if (installedTag !== null && installedTag !== compat.distTag) {
231
+ log(`本地已安装插件为 @${installedTag},与目标 @${compat.distTag} 不一致,先卸载旧版本...`);
232
+ try {
233
+ run(`echo y | openclaw plugins uninstall "${CHANNEL_ID}"`);
234
+ log("旧版本已卸载");
235
+ } catch (uninstallErr) {
236
+ error("旧版本卸载失败,请手动执行:");
237
+ if (uninstallErr.stderr) console.error(uninstallErr.stderr);
238
+ console.log(` openclaw plugins uninstall "${CHANNEL_ID}"`);
239
+ process.exit(1);
240
+ }
241
+ }
242
+
243
+ // 4. Install plugin via openclaw
244
+ log(`正在安装插件 ${pluginInstallSpec}...`);
52
245
  try {
53
- const installOut = run(`openclaw plugins install "${PLUGIN_SPEC}"`);
246
+ const installOut = run(`openclaw plugins install "${pluginInstallSpec}"`);
54
247
  if (installOut) log(installOut);
55
248
  } catch (installErr) {
56
249
  if (installErr.stderr && installErr.stderr.includes("already exists")) {
@@ -67,12 +260,16 @@ function install() {
67
260
  } else {
68
261
  error("插件安装失败,请手动执行:");
69
262
  if (installErr.stderr) console.error(installErr.stderr);
70
- console.log(` openclaw plugins install "${PLUGIN_SPEC}"`);
263
+ console.log(` openclaw plugins install "${pluginInstallSpec}"`);
71
264
  process.exit(1);
72
265
  }
73
266
  }
74
267
 
75
- // 3. Login (interactive QR scan)
268
+ // 5. Symlink host openclaw into plugin's node_modules (2026.3.22–2026.3.23 only)
269
+ // so jiti can resolve openclaw/plugin-sdk/* without a runtime dependency.
270
+ ensureOpenclawSymlink(hostVersion);
271
+
272
+ // 6. Login (interactive QR scan)
76
273
  log("插件就绪,开始首次连接...");
77
274
  try {
78
275
  run(`openclaw channels login --channel ${CHANNEL_ID}`, { silent: false });
@@ -82,7 +279,7 @@ function install() {
82
279
  console.log(` openclaw channels login --channel ${CHANNEL_ID}`);
83
280
  }
84
281
 
85
- // 4. Restart gateway so it picks up the new account
282
+ // 7. Restart gateway so it picks up the new account
86
283
  log("正在重启 OpenClaw Gateway...");
87
284
  try {
88
285
  run(`openclaw gateway restart`, { silent: false });
@@ -90,7 +287,6 @@ function install() {
90
287
  error("重启失败,可手动执行:");
91
288
  console.log(` openclaw gateway restart`);
92
289
  }
93
-
94
290
  }
95
291
 
96
292
  function help() {
@@ -98,9 +294,14 @@ function help() {
98
294
  用法: npx -y @tencent-weixin/openclaw-weixin-cli <命令>
99
295
 
100
296
  命令:
101
- install 安装微信插件并扫码连接
297
+ install 自动检测 OpenClaw 版本,安装兼容的微信插件并扫码连接
102
298
  help 显示帮助信息
103
- `);
299
+
300
+ 兼容矩阵:`);
301
+ for (const e of COMPAT_MATRIX) {
302
+ console.log(` ${e.label.padEnd(28)} OpenClaw ${formatRange(e.openclawRange)}`);
303
+ }
304
+ console.log();
104
305
  }
105
306
 
106
307
  // ── main ─────────────────────────────────────────────────────────────────────
package/lib/compat.mjs ADDED
@@ -0,0 +1,61 @@
1
+ // lib/compat.mjs
2
+ // Centralized compatibility matrix for openclaw-weixin plugin versions.
3
+
4
+ /**
5
+ * Each entry maps a plugin major version to the supported OpenClaw host range.
6
+ * Used by both the installer CLI (auto-select) and the plugin runtime (fail-fast).
7
+ */
8
+ export const COMPAT_MATRIX = [
9
+ {
10
+ pluginMajor: 1,
11
+ distTag: 'compat-host-gte2026.3.0-lt2026.3.22',
12
+ openclawRange: { gte: '2026.3.0', lt: '2026.3.22' },
13
+ label: '1.0.x (兼容轨道: OpenClaw >=2026.3.0 <2026.3.22)',
14
+ },
15
+ {
16
+ pluginMajor: 2,
17
+ distTag: 'latest',
18
+ openclawRange: { gte: '2026.3.22' },
19
+ label: '2.0.x (当前最新主线)',
20
+ },
21
+ ];
22
+
23
+ /** Parse a version string like "2026.3.22" into [major, minor, patch]. */
24
+ export function parseVersion(v) {
25
+ const m = String(v).match(/(\d+)\.(\d+)\.(\d+)/);
26
+ if (!m) return null;
27
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
28
+ }
29
+
30
+ /** Compare two version strings. Returns <0, 0, or >0. */
31
+ export function compareVersions(a, b) {
32
+ const va = parseVersion(a);
33
+ const vb = parseVersion(b);
34
+ if (!va || !vb) return NaN;
35
+ for (let i = 0; i < 3; i++) {
36
+ if (va[i] !== vb[i]) return va[i] - vb[i];
37
+ }
38
+ return 0;
39
+ }
40
+
41
+ /** Check if `version` satisfies { gte, lt } range. */
42
+ export function satisfiesRange(version, range) {
43
+ if (range.gte && compareVersions(version, range.gte) < 0) return false;
44
+ if (range.lt && compareVersions(version, range.lt) >= 0) return false;
45
+ return true;
46
+ }
47
+
48
+ /** Find the matching matrix entry for a given OpenClaw host version. */
49
+ export function findCompatEntry(openclawVersion) {
50
+ return COMPAT_MATRIX.find(entry =>
51
+ satisfiesRange(openclawVersion, entry.openclawRange)
52
+ ) || null;
53
+ }
54
+
55
+ /** Format a range for display, e.g. ">=2026.3.22 <2026.4.0". */
56
+ export function formatRange(range) {
57
+ const parts = [];
58
+ if (range.gte) parts.push(`>=${range.gte}`);
59
+ if (range.lt) parts.push(`<${range.lt}`);
60
+ return parts.join(' ');
61
+ }
@@ -0,0 +1,86 @@
1
+ // lib/version-check.mjs
2
+ // Runtime fail-fast version check — call at plugin initialization.
3
+ //
4
+ // Usage (in plugin entry point):
5
+ // import { assertHostCompat } from '@tencent/openclaw-weixin/lib/version-check.mjs';
6
+ // assertHostCompat();
7
+
8
+ import { readFileSync } from 'node:fs';
9
+ import { resolve, dirname } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import {
12
+ COMPAT_MATRIX,
13
+ findCompatEntry,
14
+ parseVersion,
15
+ formatRange,
16
+ } from './compat.mjs';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+
20
+ /**
21
+ * Resolve the current OpenClaw host version.
22
+ * Checks (in order):
23
+ * 1. OPENCLAW_VERSION env var (set by host since 2026.3.x)
24
+ * 2. globalThis.__openclaw_version__ (injected by host runtime)
25
+ */
26
+ export function getHostVersion() {
27
+ if (process.env.OPENCLAW_VERSION) return process.env.OPENCLAW_VERSION;
28
+ if (globalThis.__openclaw_version__) return String(globalThis.__openclaw_version__);
29
+ return null;
30
+ }
31
+
32
+ /**
33
+ * Read the plugin's own major version from its package.json.
34
+ */
35
+ function getPluginMajor() {
36
+ try {
37
+ const pkg = JSON.parse(
38
+ readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'),
39
+ );
40
+ return parseVersion(pkg.version)?.[0] ?? null;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Assert that the current host is compatible with this plugin version.
48
+ * Throws a clear error if incompatible; silently returns on success.
49
+ */
50
+ export function assertHostCompat() {
51
+ const hostVersion = getHostVersion();
52
+ if (!hostVersion) {
53
+ console.warn(
54
+ '[openclaw-weixin] 无法检测 OpenClaw 宿主版本,跳过兼容性检查',
55
+ );
56
+ return;
57
+ }
58
+
59
+ const pluginMajor = getPluginMajor();
60
+ const entry = COMPAT_MATRIX.find(e => e.pluginMajor === pluginMajor);
61
+ if (!entry) {
62
+ console.warn(
63
+ `[openclaw-weixin] 未知插件主版本 ${pluginMajor},跳过兼容性检查`,
64
+ );
65
+ return;
66
+ }
67
+
68
+ const compat = findCompatEntry(hostVersion);
69
+ if (compat && compat.pluginMajor === pluginMajor) {
70
+ // Compatible — nothing to do.
71
+ return;
72
+ }
73
+
74
+ const supported = formatRange(entry.openclawRange);
75
+ const suggestion = compat
76
+ ? `请安装 openclaw-weixin@${compat.distTag} (${compat.label})`
77
+ : '请升级或降级 OpenClaw 宿主到受支持的版本';
78
+
79
+ throw new Error(
80
+ `[openclaw-weixin] 宿主版本不兼容!\n` +
81
+ ` 当前 OpenClaw 版本: ${hostVersion}\n` +
82
+ ` 当前插件支持范围: ${supported}\n` +
83
+ ` ${suggestion}\n` +
84
+ ` 或运行: npx @tencent-weixin/openclaw-weixin-cli install (自动选择兼容版本)`,
85
+ );
86
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-weixin/openclaw-weixin-cli",
3
- "version": "1.0.3",
3
+ "version": "2.1.1",
4
4
  "description": "Lightweight installer for the OpenClaw Weixin channel plugin",
5
5
  "license": "MIT",
6
6
  "author": "Tencent",
@@ -9,9 +9,21 @@
9
9
  "weixin-installer": "./cli.mjs"
10
10
  },
11
11
  "files": [
12
- "cli.mjs"
12
+ "cli.mjs",
13
+ "lib/"
13
14
  ],
14
15
  "scripts": {},
16
+ "peerDependencies": {
17
+ "openclaw": ">=2026.3.0"
18
+ },
19
+ "peerDependenciesMeta": {
20
+ "openclaw": {
21
+ "optional": true
22
+ }
23
+ },
24
+ "devDependencies": {
25
+ "openclaw": "2026.3.22"
26
+ },
15
27
  "engines": {
16
28
  "node": ">=22"
17
29
  }