lightclawbot 1.1.1 → 1.1.2-beta.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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "generatedAt": "2026-04-09T11:45:37.286Z",
3
+ "cdnBase": "https://cloudcache.tencent-cloud.com/qcloud/tea/app/data/scripts",
4
+ "files": {
5
+ "upgrade.sh": {
6
+ "hashed": "upgrade.156d7999.sh",
7
+ "hash": "156d7999",
8
+ "size": 13296
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,384 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # ========== lightclawbot 插件快速安装/升级脚本 ==========
5
+ # 用途:跳过 openclaw plugins install 的慢路径,直接安装/升级 lightclawbot 插件
6
+ # 前提:openclaw 已安装且可用,pnpm 或 npm 已安装(不依赖 jq,JSON 操作统一用 node)
7
+ # 支持:首次安装 / 重装 / 升级,自动保留已有的 channels 配置
8
+ #
9
+ # 输出规范:stdout 输出多行,供前端逐行解析
10
+ # - 步骤进度:STEP:{"step":"step_id","message":"描述"}:
11
+ # 7 处 step_log 调用 分别在环境检查、版本检查、下载、解压、配置更新、设备审批、重启网关前输出
12
+ # STEP:{"step":"env_check","message":"checking environment"}
13
+ # STEP:{"step":"version_check","message":"checking version info"}
14
+ # STEP:{"step":"download","message":"downloading plugin"}
15
+ # STEP:{"step":"extract","message":"extracting and installing"}
16
+ # STEP:{"step":"config_update","message":"updating configuration"}
17
+ # STEP:{"step":"devices_approve","message":"approving devices"}
18
+ # STEP:{"step":"gateway_restart","message":"restarting gateway"}
19
+ # - 最终成功:RESULT:{"status":"ok","version":"x.y.z"}
20
+ # - 最终失败:RESULT:{"status":"error","reason":"..."}
21
+ #
22
+ # 使用方式:
23
+ # 本地执行: bash scripts/upgrade.sh
24
+ # CDN 执行: bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
25
+ # 传入参数: APIKEY="sk-xxx" bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
26
+ # 跳过重启: RESTART_GATEWAY=false APIKEY="sk-xxx" bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
27
+
28
+ # ========== 工具函数 ==========
29
+
30
+ # 输出步骤进度,供前端逐行解析
31
+ step_log() {
32
+ local step="$1"
33
+ local message="$2"
34
+ echo "STEP:{\"step\":\"${step}\",\"message\":\"${message}\"}"
35
+ }
36
+
37
+ result_exit() {
38
+ local reason="$1"
39
+ echo "RESULT:{\"status\":\"error\",\"reason\":\"${reason}\"}"
40
+ exit 1
41
+ }
42
+
43
+ result_ok() {
44
+ echo "RESULT:{\"status\":\"ok\",\"version\":\"${1}\"}"
45
+ exit 0
46
+ }
47
+
48
+ # semver 比较:left >= right 返回 0,否则返回 1,解析失败返回 2
49
+ version_gte() {
50
+ local left="$1"
51
+ local right="$2"
52
+ VERSION_LEFT="$left" VERSION_RIGHT="$right" node - <<'NODE'
53
+ function parseSemver(value) {
54
+ const match = String(value || "").trim().match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
55
+ if (!match) return null;
56
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
57
+ }
58
+ const left = parseSemver(process.env.VERSION_LEFT);
59
+ const right = parseSemver(process.env.VERSION_RIGHT);
60
+ if (!left || !right) process.exit(2);
61
+ for (let i = 0; i < 3; i++) {
62
+ if (left[i] > right[i]) process.exit(0);
63
+ if (left[i] < right[i]) process.exit(1);
64
+ }
65
+ process.exit(0);
66
+ NODE
67
+ }
68
+
69
+ # 获取本机 openclaw 版本号(纯数字 x.y.z 格式)
70
+ get_openclaw_version() {
71
+ if ! command -v openclaw &>/dev/null; then
72
+ echo ""
73
+ return
74
+ fi
75
+ local raw
76
+ raw=$(openclaw -v 2>/dev/null || true)
77
+ echo "$raw" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1
78
+ }
79
+
80
+ # ========== 配置 ==========
81
+
82
+ id="lightclawbot"
83
+ spec="lightclawbot"
84
+ cfg="$HOME/.openclaw/openclaw.json"
85
+ plugin_dir="$HOME/.openclaw/extensions/${id}"
86
+
87
+ # npm 源:使用腾讯镜像源(可通过 NPM_REGISTRY 环境变量覆盖)
88
+ NPM_REGISTRY="${NPM_REGISTRY:-https://mirrors.tencent.com/npm/}"
89
+ # fallback 源:首选失败时兜底(可通过 NPM_FALLBACK 环境变量覆盖)
90
+ NPM_FALLBACK="${NPM_FALLBACK:-https://registry.npmjs.org}"
91
+
92
+ # API Key:优先读取环境变量,为空则保留配置文件中已有的值
93
+ # 用法:APIKEY="sk-xxx" bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
94
+ APIKEY="${APIKEY:-}"
95
+
96
+ # 是否在安装/升级后重启 Gateway:默认 true,传入 false 可跳过重启
97
+ # 用法:RESTART_GATEWAY=false bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
98
+ RESTART_GATEWAY="${RESTART_GATEWAY:-true}"
99
+
100
+ # openclaw 新老版本分界线:>= 此版本使用最新插件,< 此版本锁定 LEGACY_PLUGIN_VERSION
101
+ OPENCLAW_BREAKING_VERSION="2026.3.22"
102
+ LEGACY_PLUGIN_VERSION="1.0.8"
103
+
104
+ # openclaw = 此版本需要执行 devices approve
105
+ OPENCLAW_DEVICES_APPROVE_VERSION="2026.3.28"
106
+
107
+ # ========== 环境加载 ==========
108
+ # bash xxx.sh 启动的是非交互式 shell,不会自动 source ~/.bashrc,
109
+ # 如果用户通过 nvm 安装 node,脚本中的 node/pnpm/openclaw 都找不到。
110
+
111
+ export NVM_DIR="$HOME/.nvm"
112
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
113
+
114
+ if [ -z "$PNPM_HOME" ] && [ -d "$HOME/.local/share/pnpm" ]; then
115
+ export PNPM_HOME="$HOME/.local/share/pnpm"
116
+ export PATH="$PNPM_HOME:$PATH"
117
+ fi
118
+
119
+ [ ! -t 1 ] && export CI=true
120
+
121
+ # ========== 前置检查 ==========
122
+
123
+ step_log "env_check" "checking environment"
124
+
125
+ # 1) 探测包管理器:优先 pnpm,fallback npm,都没有则报错
126
+ if command -v pnpm &>/dev/null; then
127
+ PKG_MGR="pnpm"
128
+ elif command -v npm &>/dev/null; then
129
+ PKG_MGR="npm"
130
+ else
131
+ result_exit "npm_not_found"
132
+ fi
133
+
134
+ # 2) 检查 openclaw 是否可用
135
+ if ! openclaw -v &>/dev/null; then
136
+ result_exit "openclaw_not_available"
137
+ fi
138
+
139
+ # 3) 设置 npm/pnpm 源(确保后续 pack/install 都走镜像源)
140
+ npm config set registry "$NPM_REGISTRY" 2>/dev/null || true
141
+ [ "$PKG_MGR" = "pnpm" ] && pnpm config set registry "$NPM_REGISTRY" 2>/dev/null || true
142
+
143
+ # 4) 检查 APIKEY
144
+ if [ -z "$APIKEY" ]; then
145
+ result_exit "apikey_empty"
146
+ fi
147
+
148
+ # ========== 安装 / 升级 lightclawbot ==========
149
+
150
+ step_log "version_check" "checking version info"
151
+
152
+ # ---------- 检查是否已安装,获取本地版本 ----------
153
+ local_version=""
154
+ if [ -d "$plugin_dir" ] && [ -f "$plugin_dir/package.json" ]; then
155
+ local_version=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')}catch(e){console.log('')}" "$plugin_dir/package.json" 2>/dev/null || true)
156
+ fi
157
+
158
+ # ---------- 获取 openclaw 版本,确定目标插件版本 ----------
159
+ openclaw_version=$(get_openclaw_version)
160
+ target_spec="$spec"
161
+
162
+ if [ -n "$openclaw_version" ]; then
163
+ if version_gte "$openclaw_version" "$OPENCLAW_BREAKING_VERSION"; then
164
+ # openclaw >= 2026.3.22 → 安装最新版插件
165
+ target_spec="$spec"
166
+ else
167
+ # openclaw < 2026.3.22 → 锁定旧版插件
168
+ target_spec="${spec}@${LEGACY_PLUGIN_VERSION}"
169
+ fi
170
+ fi
171
+
172
+ # ---------- 查询远程目标版本 ----------
173
+ remote_version=""
174
+
175
+ if [ -n "$openclaw_version" ] && ! version_gte "$openclaw_version" "$OPENCLAW_BREAKING_VERSION"; then
176
+ # 旧版 openclaw:版本已锁定
177
+ remote_version="$LEGACY_PLUGIN_VERSION"
178
+ else
179
+ remote_version=$(npm view "$spec" version 2>/dev/null || true)
180
+
181
+ if [ -z "$remote_version" ]; then
182
+ remote_version=$(npm view "$spec" version --registry "$NPM_FALLBACK" 2>/dev/null || true)
183
+ fi
184
+ fi
185
+
186
+ # ---------- 判断安装模式 ----------
187
+ install_mode="install"
188
+ if [ -n "$local_version" ]; then
189
+ if [ -n "$remote_version" ] && [ "$local_version" = "$remote_version" ]; then
190
+ install_mode="up-to-date"
191
+ else
192
+ install_mode="upgrade"
193
+ fi
194
+ fi
195
+
196
+ if [ "$install_mode" != "up-to-date" ]; then
197
+ # ---------- 清理旧版本 ----------
198
+ rm -rf "$plugin_dir"
199
+
200
+ step_log "download" "downloading plugin"
201
+
202
+ # ---------- npm pack 下载 tgz ----------
203
+ tmp_dir=$(mktemp -d /tmp/openclaw-plugin-XXXXXX)
204
+
205
+ tgz_file=$(cd "$tmp_dir" && npm pack "$target_spec" 2>/dev/null | tail -n1)
206
+ if [ -z "$tgz_file" ] || [ ! -f "$tmp_dir/$tgz_file" ]; then
207
+ tgz_file=$(cd "$tmp_dir" && npm pack "$target_spec" --registry "$NPM_FALLBACK" 2>/dev/null | tail -n1)
208
+ fi
209
+ if [ -z "$tgz_file" ] || [ ! -f "$tmp_dir/$tgz_file" ]; then
210
+ rm -rf "$tmp_dir"
211
+ result_exit "download_failed"
212
+ fi
213
+
214
+ step_log "extract" "extracting and installing"
215
+
216
+ # ---------- 解压到插件目录 ----------
217
+ mkdir -p "$plugin_dir"
218
+ tar -xzf "$tmp_dir/$tgz_file" -C "$plugin_dir" --strip-components=1
219
+ rm -rf "$tmp_dir"
220
+
221
+ # ---------- 安装生产依赖 ----------
222
+ # lightclawbot 声明了 bundledDependencies,npm pack 产出的 tgz 已包含所有依赖
223
+ has_unbundled_deps=$(node -e '
224
+ const pkg = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
225
+ const deps = Object.keys(pkg.dependencies || {});
226
+ const bundled = pkg.bundledDependencies || pkg.bundleDependencies || [];
227
+ console.log(deps.length > 0 && bundled.length < deps.length ? "true" : "false");
228
+ ' "$plugin_dir/package.json")
229
+
230
+ if [ "$has_unbundled_deps" = true ]; then
231
+ (cd "$plugin_dir" && CI=true $PKG_MGR install --prod) >/dev/null 2>&1
232
+ fi
233
+ fi
234
+
235
+ # ========== 更新配置文件 ==========
236
+
237
+ step_log "config_update" "updating configuration"
238
+
239
+ # 确保配置文件存在
240
+ if [ ! -f "$cfg" ]; then
241
+ mkdir -p "$(dirname "$cfg")"
242
+ echo '{}' > "$cfg"
243
+ fi
244
+
245
+ # 校验配置文件是合法 JSON(用户可能手动编辑弄坏了格式)
246
+ if ! node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$cfg" 2>/dev/null; then
247
+ result_exit "config_json_invalid"
248
+ fi
249
+
250
+ # ---------- 单次 node 调用完成所有配置更新 ----------
251
+ # 一次启动 V8,通过环境变量安全传参,
252
+ # 顺序完成 plugins.entries / installs / deny / allow / channels 全部配置修改,
253
+ # 减少文件 I/O 次数(只读一次 + 写一次)。
254
+
255
+ version=$(UPGRADE_CFG="$cfg" UPGRADE_ID="$id" UPGRADE_SPEC="$spec" \
256
+ UPGRADE_PLUGIN_DIR="$plugin_dir" UPGRADE_APIKEY="$APIKEY" \
257
+ node -e '
258
+ const fs = require("fs");
259
+ const cfgPath = process.env.UPGRADE_CFG;
260
+ const pluginId = process.env.UPGRADE_ID;
261
+ const spec = process.env.UPGRADE_SPEC;
262
+ const pluginDir = process.env.UPGRADE_PLUGIN_DIR;
263
+ const envApiKey = process.env.UPGRADE_APIKEY || "";
264
+
265
+ function fail(reason) {
266
+ process.exit(1);
267
+ }
268
+
269
+ // 读取配置
270
+ let cfg;
271
+ try {
272
+ cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
273
+ } catch (e) {
274
+ fail("config_read_failed");
275
+ }
276
+
277
+ // 读取插件版本
278
+ let version = "unknown";
279
+ try {
280
+ const pkg = JSON.parse(fs.readFileSync(pluginDir + "/package.json", "utf8"));
281
+ version = pkg.version || "unknown";
282
+ } catch (e) {}
283
+
284
+ // ---------- 1) 确保 plugins 基础结构存在 & 全局插件系统启用 ----------
285
+ if (!cfg.plugins) cfg.plugins = {};
286
+ // 确保全局插件系统生效(plugins.enabled 默认 true,仅显式 false 时需修复)
287
+ if (cfg.plugins.enabled === false) {
288
+ cfg.plugins.enabled = true;
289
+ }
290
+ if (!cfg.plugins.entries) cfg.plugins.entries = {};
291
+ if (!cfg.plugins.installs) cfg.plugins.installs = {};
292
+
293
+ // plugins.entries:启用插件
294
+ cfg.plugins.entries[pluginId] = { enabled: true };
295
+
296
+ // plugins.installs:安装记录
297
+ cfg.plugins.installs[pluginId] = {
298
+ source: "npm",
299
+ spec: spec,
300
+ installPath: pluginDir,
301
+ version: version,
302
+ installedAt: new Date().toISOString().replace(/\.\d{3}Z$/, ".000Z")
303
+ };
304
+
305
+ // ---------- 2) 黑名单修复 ----------
306
+ if (Array.isArray(cfg.plugins.deny)) {
307
+ const idx = cfg.plugins.deny.indexOf(pluginId);
308
+ if (idx !== -1) {
309
+ cfg.plugins.deny.splice(idx, 1);
310
+ if (cfg.plugins.deny.length === 0) {
311
+ delete cfg.plugins.deny;
312
+ }
313
+ }
314
+ }
315
+
316
+ // ---------- 3) 白名单添加 ----------
317
+ if (Array.isArray(cfg.plugins.allow)) {
318
+ if (!cfg.plugins.allow.includes(pluginId)) {
319
+ cfg.plugins.allow.push(pluginId);
320
+ }
321
+ }
322
+
323
+ // ---------- 4) channel 配置:智能合并 ----------
324
+ if (!cfg.channels) cfg.channels = {};
325
+ const ch = cfg.channels[pluginId] || {};
326
+
327
+ // 迁移旧的 apiKey -> apiKeys
328
+ let apiKeys = Array.isArray(ch.apiKeys) ? [...ch.apiKeys] : [];
329
+ if (ch.apiKey && typeof ch.apiKey === "string") {
330
+ if (!apiKeys.includes(ch.apiKey)) {
331
+ apiKeys.push(ch.apiKey);
332
+ }
333
+ delete ch.apiKey;
334
+ }
335
+
336
+ // 如果通过环境变量传入了 APIKEY,确保它在 apiKeys 数组中(去重)
337
+ if (envApiKey && !apiKeys.includes(envApiKey)) {
338
+ apiKeys.push(envApiKey);
339
+ }
340
+
341
+ ch.enabled = true;
342
+ ch.apiKeys = apiKeys;
343
+ cfg.channels[pluginId] = ch;
344
+
345
+ // ---------- 5) 写回配置文件 ----------
346
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
347
+
348
+ // 输出版本号到 stdout,供 shell 接收
349
+ process.stdout.write(version);
350
+ ') || result_exit "config_update_failed"
351
+
352
+ # ========== 插件 >= 1.1.0 时需要重新安装依赖并注册 ==========
353
+
354
+ # if version_gte "$version" "1.1.0"; then
355
+ # (cd "$plugin_dir" && pnpm install) >/dev/null 2>&1 || true
356
+ # openclaw gateway install --force >/dev/null 2>&1 || true
357
+
358
+ # # 小内存机器(<3GB)自动注入 NODE_OPTIONS 限制堆内存,避免 OOM
359
+ # GATEWAY_SERVICE="$HOME/.config/systemd/user/openclaw-gateway.service"
360
+ # TOTAL_MEM_KB=$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null || echo "0")
361
+ # if [ "$TOTAL_MEM_KB" -gt 0 ] && [ "$TOTAL_MEM_KB" -lt 3000000 ] \
362
+ # && [ -f "$GATEWAY_SERVICE" ] \
363
+ # && ! grep -q 'max-old-space-size' "$GATEWAY_SERVICE"; then
364
+ # sed -i '/\[Service\]/a Environment="NODE_OPTIONS=--max-old-space-size=1500"' "$GATEWAY_SERVICE"
365
+ # systemctl --user daemon-reload
366
+ # fi
367
+ # fi
368
+
369
+ # ========== 设备审批(openclaw == 2026.3.28)==========
370
+
371
+ if [ -n "$openclaw_version" ] && [ "$openclaw_version" = "$OPENCLAW_DEVICES_APPROVE_VERSION" ]; then
372
+ step_log "devices_approve" "approving devices"
373
+ openclaw devices approve >/dev/null 2>&1 || true
374
+ fi
375
+
376
+ # ========== 重启 Gateway ==========
377
+
378
+ if [ "$RESTART_GATEWAY" = "true" ]; then
379
+ step_log "gateway_restart" "restarting gateway"
380
+ openclaw gateway restart >/dev/null 2>&1 || true
381
+ fi
382
+
383
+ # stdout 最后一行 RESULT,供前端解析最终结果
384
+ result_ok "$version"