openclaw-xiaoyou 1.0.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,427 @@
1
+ #!/usr/bin/env bash
2
+ # install-xiayou.sh — OpenClaw xiaoyou channel 一键安装/升级/卸载脚本
3
+ #
4
+ # 用法:
5
+ # install-xiayou.sh --ws-url <url> --token <token>
6
+ # install-xiayou.sh --version <ver> --ws-url <url> --token <token>
7
+ # install-xiayou.sh uninstall
8
+ # install-xiayou.sh --check-only
9
+
10
+ if [ -z "${BASH_VERSION:-}" ] || shopt -oq posix 2>/dev/null; then
11
+ if command -v bash >/dev/null 2>&1; then
12
+ exec bash "$0" "$@"
13
+ fi
14
+ printf '[xiaoyou-install][error] install-xiayou.sh 需要 bash\n' >&2
15
+ exit 1
16
+ fi
17
+
18
+ set -Eeuo pipefail
19
+
20
+ SCRIPT_NAME="$(basename "$0")"
21
+ WS_URL=""
22
+ TOKEN=""
23
+ CHECK_ONLY=0
24
+ ACTION="install"
25
+ REQUESTED_VERSION=""
26
+ PLUGIN_NAME="openclaw-xiaoyou"
27
+ CHANNEL_KEY="xiaoyou"
28
+ STATUS_TIMEOUT="${XIAOYOU_INSTALL_STATUS_TIMEOUT:-30}"
29
+ LIST_TIMEOUT="${XIAOYOU_INSTALL_LIST_TIMEOUT:-30}"
30
+ LATEST_TIMEOUT="${XIAOYOU_INSTALL_LATEST_TIMEOUT:-15}"
31
+ OPENCLAW_PROCESS_PATTERN='openclaw-gateway|(^|[ /])openclaw[[:space:]]+gateway([[:space:]]|$)|(^|[ /])openclaw$'
32
+
33
+ # -----------------------------------------------------------------------------
34
+ # Logging helpers
35
+ # -----------------------------------------------------------------------------
36
+
37
+ log() { printf '[xiaoyou-install] %s\n' "$*"; }
38
+ warn() { printf '[xiaoyou-install][warn] %s\n' "$*" >&2; }
39
+ die() { printf '[xiaoyou-install][error] %s\n' "$*" >&2; exit 1; }
40
+
41
+ phase_start() {
42
+ printf '[xiaoyou-install][phase.start] name=%s\n' "$1"
43
+ printf '[xiaoyou-install][phase.start] will_do=%s\n' "$2"
44
+ printf '[xiaoyou-install][phase.start] why=%s\n' "$3"
45
+ }
46
+
47
+ phase_end() {
48
+ printf '[xiaoyou-install][phase.end] name=%s\n' "$1"
49
+ printf '[xiaoyou-install][phase.end] result=%s\n' "$2"
50
+ printf '[xiaoyou-install][phase.end] summary=%s\n' "$3"
51
+ }
52
+
53
+ command_exists() { command -v "$1" >/dev/null 2>&1; }
54
+
55
+ run() { log "run: $*"; "$@"; }
56
+
57
+ run_or_dry_run() {
58
+ if [[ "$CHECK_ONLY" -eq 1 ]]; then log "dry-run: $*"; return 0; fi
59
+ run "$@"
60
+ }
61
+
62
+ capture_or_empty() { "$@" 2>/dev/null || true; }
63
+
64
+ capture_timeout_or_empty() {
65
+ local seconds="$1"; shift
66
+ if command_exists perl; then
67
+ perl -e '
68
+ use POSIX ":sys_wait_h";
69
+ my $timeout = shift @ARGV;
70
+ my $pid = fork(); exit 127 unless defined $pid;
71
+ if ($pid == 0) { exec @ARGV; exit 127; }
72
+ my $deadline = time() + $timeout;
73
+ while (1) {
74
+ my $done = waitpid($pid, WNOHANG);
75
+ if ($done == $pid) { exit($? >> 8); }
76
+ if (time() >= $deadline) { kill "TERM", $pid; sleep 1; kill "KILL", $pid; waitpid($pid, 0); exit 124; }
77
+ select undef, undef, undef, 0.1;
78
+ }
79
+ ' "$seconds" "$@" 2>/dev/null || true
80
+ else
81
+ "$@" 2>/dev/null || true
82
+ fi
83
+ }
84
+
85
+ # -----------------------------------------------------------------------------
86
+ # Argument parsing
87
+ # -----------------------------------------------------------------------------
88
+
89
+ usage() {
90
+ cat <<'USAGE'
91
+ Usage:
92
+ install-xiayou.sh --ws-url <url> --token <token>
93
+ install-xiayou.sh --version <ver> --ws-url <url> --token <token>
94
+ install-xiayou.sh uninstall
95
+ install-xiayou.sh --check-only
96
+
97
+ Options:
98
+ --ws-url <url> 企业 WebSocket 服务地址 (wss://... 或 ws://...)
99
+ --token <token> 企业服务认证 Token
100
+ --version <ver> 指定 openclaw-xiaoyou 版本 (默认 latest)
101
+ --check-only 只输出计划,不执行
102
+ uninstall 卸载 xiaoyou channel
103
+ USAGE
104
+ }
105
+
106
+ parse_args() {
107
+ while [[ $# -gt 0 ]]; do
108
+ case "$1" in
109
+ -h|--help) usage; exit 0 ;;
110
+ --ws-url)
111
+ [[ $# -ge 2 ]] || die "--ws-url 缺少参数"
112
+ WS_URL="$2"; shift 2 ;;
113
+ --token)
114
+ [[ $# -ge 2 ]] || die "--token 缺少参数"
115
+ TOKEN="$2"; shift 2 ;;
116
+ --version)
117
+ [[ $# -ge 2 ]] || die "--version 缺少参数"
118
+ REQUESTED_VERSION="$2"; shift 2 ;;
119
+ --check-only) CHECK_ONLY=1; shift ;;
120
+ uninstall) ACTION="uninstall"; shift ;;
121
+ --) shift; break ;;
122
+ -*) die "未知参数: $1" ;;
123
+ *)
124
+ # 兼容位置参数传 token
125
+ if [[ -z "$TOKEN" ]]; then TOKEN="$1"; shift
126
+ else die "多余的位置参数: $1"; fi ;;
127
+ esac
128
+ done
129
+ }
130
+
131
+ validate_ws_url() {
132
+ [[ -z "$WS_URL" ]] && return 0
133
+ if [[ ! "$WS_URL" =~ ^wss?:// ]]; then
134
+ die "--ws-url 格式不合法,必须以 ws:// 或 wss:// 开头。当前值: $WS_URL"
135
+ fi
136
+ }
137
+
138
+ # -----------------------------------------------------------------------------
139
+ # Version helpers
140
+ # -----------------------------------------------------------------------------
141
+
142
+ extract_semver() {
143
+ printf '%s\n' "$1" | grep -Eo '[0-9]+(\.[0-9]+){1,2}' | head -n 1 || true
144
+ }
145
+
146
+ normalize_semver() {
147
+ local v="$1"; local major minor patch
148
+ IFS='.' read -r major minor patch <<<"$v"
149
+ printf '%s.%s.%s\n' "${major:-0}" "${minor:-0}" "${patch:-0}"
150
+ }
151
+
152
+ # -----------------------------------------------------------------------------
153
+ # Platform detection
154
+ # -----------------------------------------------------------------------------
155
+
156
+ process_lines() {
157
+ ps -axww -o pid,command 2>/dev/null || ps -axo pid,command 2>/dev/null || ps -ef 2>/dev/null || true
158
+ }
159
+
160
+ openclaw_gateway_running() {
161
+ process_lines | grep -E "$OPENCLAW_PROCESS_PATTERN" | grep -v grep | grep -v 'install-xiayou' >/dev/null 2>&1
162
+ }
163
+
164
+ openclaw_installed() {
165
+ command_exists openclaw || [[ -d "$HOME/.openclaw" ]]
166
+ }
167
+
168
+ openclaw_version() {
169
+ local raw version
170
+ raw="$(capture_or_empty openclaw --version)"
171
+ version="$(extract_semver "$raw")"
172
+ [[ -n "$version" ]] || die "无法解析 OpenClaw 版本: $raw"
173
+ normalize_semver "$version"
174
+ }
175
+
176
+ # -----------------------------------------------------------------------------
177
+ # Read existing config
178
+ # -----------------------------------------------------------------------------
179
+
180
+ openclaw_config_file() {
181
+ local primary="$HOME/.openclaw/openclaw.json"
182
+ local legacy="$HOME/.openclaw/config.json"
183
+ if [[ -f "$primary" || ! -f "$legacy" ]]; then printf '%s\n' "$primary"
184
+ else printf '%s\n' "$legacy"; fi
185
+ }
186
+
187
+ existing_ws_url() {
188
+ local cfg; cfg="$(openclaw_config_file)"
189
+ [[ -f "$cfg" ]] || return 0
190
+ cat "$cfg" 2>/dev/null | grep -Eo '"wsUrl"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n 1 | sed -E 's/.*"wsUrl"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/' || true
191
+ }
192
+
193
+ existing_auth_token() {
194
+ local cfg; cfg="$(openclaw_config_file)"
195
+ [[ -f "$cfg" ]] || return 0
196
+ cat "$cfg" 2>/dev/null | grep -Eo '"authToken"[[:space:]]*:[[:space:]]*"[^"]*"' | head -n 1 | sed -E 's/.*"authToken"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/' || true
197
+ }
198
+
199
+ installed_plugin_version() {
200
+ local list line
201
+ list="$(capture_timeout_or_empty "$LIST_TIMEOUT" openclaw plugins list)"
202
+ line="$(printf '%s\n' "$list" | grep -E "$PLUGIN_NAME" | head -n 1 || true)"
203
+ [[ -n "$line" ]] || return 0
204
+ printf '%s\n' "$line" | grep -Eo '[0-9]+(\.[0-9]+){1,2}' | head -n 1 || true
205
+ }
206
+
207
+ npm_latest_version() {
208
+ capture_timeout_or_empty "$LATEST_TIMEOUT" npm view "$PLUGIN_NAME" version
209
+ }
210
+
211
+ # -----------------------------------------------------------------------------
212
+ # Install / Upgrade
213
+ # -----------------------------------------------------------------------------
214
+
215
+ install_or_upgrade() {
216
+ phase_start \
217
+ "xiaoyou.install-or-upgrade" \
218
+ "安装或升级 $PLUGIN_NAME 插件,并写入 xiaoyou channel 配置" \
219
+ "需要确保插件版本可用,且 wsUrl/authToken 配置完整"
220
+
221
+ command_exists openclaw || die "未找到 openclaw 命令,请先安装 OpenClaw"
222
+
223
+ local version current latest should_install=0 target final_summary=""
224
+ version="$(openclaw_version)"
225
+ log "OpenClaw version: $version"
226
+
227
+ # 确定目标包
228
+ if [[ -n "$REQUESTED_VERSION" ]]; then
229
+ target="${PLUGIN_NAME}@${REQUESTED_VERSION}"
230
+ else
231
+ target="${PLUGIN_NAME}@latest"
232
+ fi
233
+ log "target package: $target"
234
+
235
+ # 检查已安装版本
236
+ current="$(installed_plugin_version || true)"
237
+ current="${current:-}"
238
+ log "installed $PLUGIN_NAME version: ${current:-<none>}"
239
+
240
+ # 决定是否需要安装
241
+ if [[ -z "$current" ]]; then
242
+ log "未检测到 $PLUGIN_NAME 插件,进入安装流程"
243
+ should_install=1
244
+ elif [[ -n "$REQUESTED_VERSION" ]]; then
245
+ if [[ "$current" == "$REQUESTED_VERSION" ]]; then
246
+ log "已安装指定版本 $REQUESTED_VERSION,无需重新安装"
247
+ else
248
+ log "当前 ${current},目标 ${REQUESTED_VERSION},需要升级"
249
+ should_install=1
250
+ fi
251
+ else
252
+ latest="$(npm_latest_version || true)"
253
+ latest="${latest:-}"
254
+ log "npm latest $PLUGIN_NAME: ${latest:-<unknown>}"
255
+ if [[ -z "$latest" ]]; then
256
+ warn "无法查询 npm latest,执行幂等安装"
257
+ should_install=1
258
+ elif [[ "$current" != "$latest" ]]; then
259
+ log "当前 ${current},latest ${latest},需要升级"
260
+ should_install=1
261
+ else
262
+ log "$PLUGIN_NAME 已是最新版本: $current"
263
+ fi
264
+ fi
265
+
266
+ # 首次安装必须有 ws-url 和 token
267
+ if [[ -z "$current" && "$CHECK_ONLY" -eq 0 ]]; then
268
+ if [[ -z "$WS_URL" ]]; then
269
+ die "首次安装必须提供 --ws-url。请运行: $SCRIPT_NAME --ws-url <url> --token <token>"
270
+ fi
271
+ if [[ -z "$TOKEN" ]]; then
272
+ die "首次安装必须提供 --token。请运行: $SCRIPT_NAME --ws-url <url> --token <token>"
273
+ fi
274
+ fi
275
+
276
+ # 升级时复用现有配置
277
+ if [[ -n "$current" ]]; then
278
+ if [[ -z "$WS_URL" ]]; then
279
+ WS_URL="$(existing_ws_url || true)"
280
+ WS_URL="${WS_URL:-}"
281
+ [[ -n "$WS_URL" ]] && log "复用现有 wsUrl: $WS_URL"
282
+ fi
283
+ if [[ -z "$TOKEN" ]]; then
284
+ TOKEN="$(existing_auth_token || true)"
285
+ TOKEN="${TOKEN:-}"
286
+ [[ -n "$TOKEN" ]] && log "复用现有 authToken"
287
+ fi
288
+ fi
289
+
290
+ # 执行
291
+ if [[ "$CHECK_ONLY" -eq 1 ]]; then
292
+ if [[ "$should_install" -eq 1 ]]; then
293
+ final_summary="check-only:需要安装 $target,但当前只输出计划"
294
+ else
295
+ final_summary="check-only:当前版本满足要求,无需安装或升级"
296
+ fi
297
+ log "$final_summary"
298
+ elif [[ "$should_install" -eq 1 ]]; then
299
+ if [[ -n "$current" ]]; then
300
+ log "升级流程:先卸载旧插件,再安装新版本"
301
+ xiayou_uninstall_plugin_only
302
+ fi
303
+ run env NPM_CONFIG_LEGACY_PEER_DEPS=true openclaw plugins install "$target"
304
+ final_summary="已安装 $target"
305
+ else
306
+ final_summary="无需安装或升级"
307
+ fi
308
+
309
+ # 写入配置
310
+ if [[ "$CHECK_ONLY" -eq 0 ]]; then
311
+ patch_config
312
+ final_summary="${final_summary},已写入 channel 配置"
313
+ fi
314
+
315
+ xiayou_status
316
+ phase_end "xiaoyou.install-or-upgrade" "ok" "$final_summary"
317
+ }
318
+
319
+ patch_config() {
320
+ [[ -n "$WS_URL" || -n "$TOKEN" ]] || return 0
321
+
322
+ log "写入 xiaoyou channel 配置"
323
+ run openclaw config set "channels.${CHANNEL_KEY}.enabled" true --strict-json
324
+
325
+ if [[ -n "$WS_URL" ]]; then
326
+ run openclaw config set "channels.${CHANNEL_KEY}.wsUrl" "$WS_URL"
327
+ fi
328
+ if [[ -n "$TOKEN" ]]; then
329
+ run openclaw config set "channels.${CHANNEL_KEY}.authToken" "$TOKEN"
330
+ fi
331
+
332
+ run openclaw config set "channels.${CHANNEL_KEY}.dmSecurity" open
333
+ run openclaw config set "channels.${CHANNEL_KEY}.reconnectIntervalMs" 3000 --strict-json
334
+ run openclaw config set "channels.${CHANNEL_KEY}.heartbeatIntervalMs" 30000 --strict-json
335
+ run openclaw config set "channels.${CHANNEL_KEY}.heartbeatTimeoutMs" 10000 --strict-json
336
+
337
+ local config_file
338
+ config_file="$(capture_or_empty openclaw config file)"
339
+ log "已写入配置: ${config_file:-$(openclaw_config_file)}"
340
+ }
341
+
342
+ # -----------------------------------------------------------------------------
343
+ # Status
344
+ # -----------------------------------------------------------------------------
345
+
346
+ xiayou_status() {
347
+ phase_start "xiaoyou.status" "检查 xiaoyou channel 状态" "确认插件已被识别且 gateway 在运行"
348
+ log "状态检测"
349
+
350
+ if openclaw_gateway_running; then
351
+ local output
352
+ output="$(capture_timeout_or_empty "$STATUS_TIMEOUT" openclaw status)"
353
+ if [[ -n "$output" ]]; then
354
+ printf '%s\n' "$output"
355
+ phase_end "xiaoyou.status" "ok" "gateway 运行中,已输出状态"
356
+ else
357
+ warn "openclaw status 在 ${STATUS_TIMEOUT}s 内未返回"
358
+ phase_end "xiaoyou.status" "warning" "gateway 运行中但 status 超时"
359
+ fi
360
+ else
361
+ capture_timeout_or_empty "$LIST_TIMEOUT" openclaw plugins list | grep -E "$PLUGIN_NAME|$CHANNEL_KEY" || true
362
+ capture_timeout_or_empty "$STATUS_TIMEOUT" openclaw channels list | grep -Ei "$CHANNEL_KEY|xiaoyou" || true
363
+ warn "未检测到运行中的 OpenClaw gateway;请运行 openclaw gateway 或 openclaw gateway restart"
364
+ phase_end "xiaoyou.status" "warning" "gateway 未运行,已退回到插件/渠道列表检查"
365
+ fi
366
+ }
367
+
368
+ # -----------------------------------------------------------------------------
369
+ # Uninstall
370
+ # -----------------------------------------------------------------------------
371
+
372
+ xiayou_uninstall_plugin_only() {
373
+ # 仅删除插件记录和文件,不删除配置(升级时用)
374
+ if command_exists openclaw; then
375
+ run_or_dry_run openclaw config unset "plugins.entries.${PLUGIN_NAME}" || true
376
+ run_or_dry_run openclaw config unset "plugins.installs.${PLUGIN_NAME}" || true
377
+ fi
378
+ run_or_dry_run rm -rf "$HOME/.openclaw/extensions/${PLUGIN_NAME}" "$HOME"/.openclaw/extensions/.openclaw-* || true
379
+ }
380
+
381
+ xiayou_uninstall() {
382
+ phase_start "xiaoyou.uninstall" "删除 xiaoyou channel 配置、插件记录和扩展目录" "清理所有 xiaoyou 相关状态"
383
+
384
+ if ! openclaw_installed; then
385
+ log "未检测到 OpenClaw,跳过卸载"
386
+ phase_end "xiaoyou.uninstall" "skipped" "未检测到 OpenClaw"
387
+ return 0
388
+ fi
389
+
390
+ log "开始卸载 xiaoyou channel"
391
+ if command_exists openclaw; then
392
+ run_or_dry_run openclaw config unset "channels.${CHANNEL_KEY}" || warn "删除 channels.${CHANNEL_KEY} 失败"
393
+ run_or_dry_run openclaw config unset "plugins.entries.${PLUGIN_NAME}" || warn "删除 plugins.entries 失败"
394
+ run_or_dry_run openclaw config unset "plugins.installs.${PLUGIN_NAME}" || warn "删除 plugins.installs 失败"
395
+ fi
396
+
397
+ run_or_dry_run rm -rf "$HOME/.openclaw/extensions/${PLUGIN_NAME}" "$HOME"/.openclaw/extensions/.openclaw-* || warn "删除插件文件失败"
398
+
399
+ if [[ "$CHECK_ONLY" -eq 1 ]]; then
400
+ phase_end "xiaoyou.uninstall" "planned" "check-only:已输出计划,未执行"
401
+ else
402
+ phase_end "xiaoyou.uninstall" "ok" "已完成 xiaoyou channel 卸载"
403
+ fi
404
+ }
405
+
406
+ # -----------------------------------------------------------------------------
407
+ # Entrypoint
408
+ # -----------------------------------------------------------------------------
409
+
410
+ main() {
411
+ phase_start "main.entry" "解析参数并进入安装或卸载分支" "调度入口"
412
+ parse_args "$@"
413
+ validate_ws_url
414
+
415
+ if [[ "$ACTION" == "uninstall" ]]; then
416
+ xiayou_uninstall
417
+ phase_end "main.entry" "ok" "卸载流程完成"
418
+ return 0
419
+ fi
420
+
421
+ install_or_upgrade
422
+
423
+ log "完成。企业服务端应能收到来自 xiaoyou 插件的 WebSocket 连接。"
424
+ phase_end "main.entry" "ok" "安装流程完成"
425
+ }
426
+
427
+ main "$@"