Skill: ryanlee-gemini/qqbot-installer (version 1.0.3)

--- name: QQBot Installer description: OpenClaw QQ 机器人插件安装与升级助手。当用户说「安装 qqbot」「升级 qqbot 插件」「更新 openclaw-qqbot」「qqbot 插件怎么装」「帮我安装/升级 qqbot」等相关语句时激活。支持全新安装和版本升级，含文件验证、自动回滚、重启。 --- # OpenClaw QQ 机器人插件安装 & 升级 通过 `scripts/upgrade-plugin.sh` 在 agent 内直接完成插件的安装或升级，无需用户在终端手动操作。 ## 核心脚本 `scripts/upgrade-plugin.sh` — 通用插件安装/升级脚本。 ``` 用法: upgrade-plugin.sh <npm-pkg-name> <plugin-id> [选项] 必填： <npm-pkg-name> npm 包名，如 openclaw-qqbot <plugin-id> 插件目录名，如 openclaw-qqbot 选项： --version <ver> 指定版本（跳过 update，走 reinstall） --no-restart 不重启 gateway（热更新场景） --verify-files <files> 逗号分隔的相对路径，验证这些文件存在 --legacy-dirs <dirs> 逗号分隔的旧目录名，安装前清理 ``` ## 执行逻辑 1. **策略判断**：有安装记录 + 有目录 + 未指定版本 → `plugins update`（升级）；否则 → backup + `plugins install --pin`（全新安装） 2. **文件验证**：读取 `package.json` 版本号，检查 `--verify-files` 指定的关键文件 3. **postinstall**：若存在 `scripts/postinstall-link-sdk.js` 自动执行 4. **重启**：执行 `gateway restart` 使插件生效（`--no-restart` 跳过） ## QQ 机器人插件（openclaw-qqbot） **安装或升级到最新版：** ```bash bash <skill_dir>/scripts/upgrade-plugin.sh \ "openclaw-qqbot" \ "openclaw-qqbot" \ --verify-files "dist/index.js,dist/src/gateway.js,dist/src/api.js,dist/src/admin-resolver.js" \ --legacy-dirs "qqbot,openclaw-qq" ``` **安装或升级到指定版本：** ```bash bash <skill_dir>/scripts/upgrade-plugin.sh \ "openclaw-qqbot" \ "openclaw-qqbot" \ --version "1.2.3" \ --verify-files "dist/index.js,dist/src/gateway.js,dist/src/api.js,dist/src/admin-resolver.js" \ --legacy-dirs "qqbot,openclaw-qq" ``` > `<skill_dir>` = 本 skill 文件所在目录，即 `SKILL.md` 的 dirname。 > 执行前需用 `exec` 运行脚本，并将输出展示给用户。 ## 执行后处理 脚本输出包含结构化行： - `PLUGIN_NEW_VERSION=1.2.3` → 新版本号 - `PLUGIN_REPORT=✅ ...` → 结果摘要（直接转发给用户） 若脚本退出码非 0，告知用户操作失败并粘贴输出，建议检查网络和 npm registry。 ## 通用插件（非 qqbot） ```bash bash <skill_dir>/scripts/upgrade-plugin.sh \ "my-org/my-openclaw-plugin" \ "my-plugin-id" ```

path: scripts/upgrade-plugin.sh
content: #!/bin/bash
# plugin-upgrader/scripts/upgrade-plugin.sh
#
# 通用 OpenClaw 插件升级脚本
#
# 功能：
#   - 自动检测 openclaw CLI (openclaw / clawdbot / moltbot)
#   - 已安装 → plugins update；未安装 / 指定版本 → rm + plugins install
#   - 安装后验证 package.json 中版本号可读
#   - 重启 gateway 使新版生效
#
# 用法：
#   upgrade-plugin.sh <npm-pkg-name> <plugin-id> [--version <ver>] [--no-restart] [--verify-files <file1,file2,...>]
#
# 参数：
#   <npm-pkg-name>           npm 包名，如 my-org/openclaw-qqbot
#   <plugin-id>              插件目录名，如 openclaw-qqbot
#   --version <ver>          指定版本（跳过 update，走 reinstall）
#   --no-restart             不重启 gateway（热更新场景）
#   --verify-files <files>   逗号分隔的文件相对路径，验证是否存在（可选）
#   --legacy-dirs <dirs>     逗号分隔的历史遗留目录名，安装前删除（可选）
#
# 退出码：
#   0 - 成功
#   1 - 失败（详见 stderr）

set -eo pipefail

PKG_NAME="${1:-}"
PLUGIN_ID="${2:-}"

if [ -z "$PKG_NAME" ] || [ -z "$PLUGIN_ID" ]; then
    echo "用法: $0 <npm-pkg-name> <plugin-id> [--version <ver>] [--no-restart] [--verify-files file1,file2] [--legacy-dirs dir1,dir2]"
    exit 1
fi

shift 2

TARGET_VERSION=""
NO_RESTART=false
VERIFY_FILES=""
LEGACY_DIRS=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        --version)
            [ -z "$2" ] && echo "❌ --version 需要参数" && exit 1
            TARGET_VERSION="${2#v}"
            shift 2
            ;;
        --no-restart)
            NO_RESTART=true
            shift
            ;;
        --verify-files)
            [ -z "$2" ] && echo "❌ --verify-files 需要参数" && exit 1
            VERIFY_FILES="$2"
            shift 2
            ;;
        --legacy-dirs)
            [ -z "$2" ] && echo "❌ --legacy-dirs 需要参数" && exit 1
            LEGACY_DIRS="$2"
            shift 2
            ;;
        *)
            echo "未知选项: $1"
            exit 1
            ;;
    esac
done

INSTALL_SRC="${PKG_NAME}@${TARGET_VERSION:-latest}"

# 检测 CLI
CMD=""
for name in openclaw clawdbot moltbot; do
    command -v "$name" &>/dev/null && CMD="$name" && break
done
[ -z "$CMD" ] && echo "❌ 未找到 openclaw / clawdbot / moltbot CLI" && exit 1

OPENCLAW_VERSION="$($CMD --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || true)"

# 动态获取 global extensions 目录
EXTENSIONS_DIR="$($CMD plugins list 2>/dev/null | grep -oE 'global: [^ ]+' | head -1 | sed 's/global: //' || true)"
if [ -z "$EXTENSIONS_DIR" ]; then
    EXTENSIONS_DIR="$HOME/.openclaw/extensions"
fi

echo "==========================================="
echo "  插件升级: $INSTALL_SRC"
echo "  CLI: $CMD  版本: ${OPENCLAW_VERSION:-unknown}"
echo "==========================================="
echo ""

# 记录旧版本
OLD_PKG="$EXTENSIONS_DIR/$PLUGIN_ID/package.json"
OLD_VERSION=""
[ -f "$OLD_PKG" ] && OLD_VERSION="$(node -e "try{const v=JSON.parse(require('fs').readFileSync('$OLD_PKG','utf8')).version;if(v)process.stdout.write(String(v));}catch{}" 2>/dev/null || true)"
[ -n "$OLD_VERSION" ] && echo "  当前版本: $OLD_VERSION"

# 动态定位配置文件
DATA_DIR="$(dirname "$EXTENSIONS_DIR")"
CONFIG_FILE=""
for _name in "$CMD.json" "openclaw.json"; do
    _f="$DATA_DIR/$_name"
    if [ -f "$_f" ]; then CONFIG_FILE="$_f"; break; fi
done
[ -z "$CONFIG_FILE" ] && CONFIG_FILE="$HOME/.openclaw/openclaw.json"

# [1/4] 安装/升级
echo ""
echo "[1/4] 安装/升级插件..."

# 决策：有配置记录 + 有目录 + 未指定版本 → update，否则 → reinstall
HAS_INSTALL_RECORD="$(node -e "
  try {
    const cfg=JSON.parse(require('fs').readFileSync('$CONFIG_FILE','utf8'));
    const inst=cfg.plugins&&cfg.plugins.installs&&cfg.plugins.installs['$PLUGIN_ID'];
    if(inst) process.stdout.write('yes');
  } catch {}
" 2>/dev/null || true)"
HAS_PLUGIN_DIR=false
[ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ] && [ -f "$EXTENSIONS_DIR/$PLUGIN_ID/package.json" ] && HAS_PLUGIN_DIR=true

UPGRADE_OK=false

if [ "$HAS_INSTALL_RECORD" = "yes" ] && [ "$HAS_PLUGIN_DIR" = "true" ] && [ -z "$TARGET_VERSION" ]; then
    echo "  [策略] 已安装 + 未指定版本 → plugins update"
    if $CMD plugins update "$PLUGIN_ID" 2>&1; then
        POST_VER="$(node -e "try{const v=JSON.parse(require('fs').readFileSync('$OLD_PKG','utf8')).version;if(v)process.stdout.write(String(v));}catch{}" 2>/dev/null || true)"
        if [ -z "$OLD_VERSION" ] || [ "$POST_VER" != "$OLD_VERSION" ]; then
            UPGRADE_OK=true
            echo "  ✅ update 成功${OLD_VERSION:+ ($OLD_VERSION → $POST_VER)}"
        else
            echo "  ⚠️  版本未变 ($POST_VER)，回退到 reinstall..."
        fi
    else
        echo "  ⚠️  update 失败，回退到 reinstall..."
    fi
fi

if [ "$UPGRADE_OK" != "true" ]; then
    echo "  [策略] 使用 reinstall: $INSTALL_SRC"

    # 备份旧目录
    BACKUP_DIR=""
    if [ -d "$EXTENSIONS_DIR/$PLUGIN_ID" ]; then
        BACKUP_DIR="$EXTENSIONS_DIR/.$PLUGIN_ID-backup-$$"
        mv "$EXTENSIONS_DIR/$PLUGIN_ID" "$BACKUP_DIR"
        echo "  已备份: $BACKUP_DIR"
    fi

    # 清理历史遗留目录
    if [ -n "$LEGACY_DIRS" ]; then
        IFS=',' read -ra LDIRS <<< "$LEGACY_DIRS"
        for d in "${LDIRS[@]}"; do
            [ -d "$EXTENSIONS_DIR/$d" ] && rm -rf "$EXTENSIONS_DIR/$d" && echo "  已清理历史目录: $d"
        done
    fi

    if $CMD plugins install "$INSTALL_SRC" --pin 2>&1; then
        UPGRADE_OK=true
        echo "  ✅ install 成功"
        [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ] && rm -rf "$BACKUP_DIR" && echo "  已清理旧版备份"
    else
        echo "  ❌ install 失败"
        [ -n "$BACKUP_DIR" ] && [ -d "$BACKUP_DIR" ] && mv "$BACKUP_DIR" "$EXTENSIONS_DIR/$PLUGIN_ID" && echo "  ↩️  已回滚"
        exit 1
    fi
fi

# [2/4] 验证安装
echo ""
echo "[2/4] 验证安装..."

TARGET_DIR="$EXTENSIONS_DIR/$PLUGIN_ID"
PKG_JSON="$TARGET_DIR/package.json"
NEW_VERSION=""
VERIFY_OK=true

if [ -f "$PKG_JSON" ]; then
    NEW_VERSION="$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')" "$PKG_JSON" 2>/dev/null || true)"
fi

if [ -z "$NEW_VERSION" ]; then
    echo "  ❌ 无法读取版本号"
    VERIFY_OK=false
else
    echo "  ✅ 版本号: $NEW_VERSION"
fi

# 验证自定义文件列表
if [ -n "$VERIFY_FILES" ]; then
    IFS=',' read -ra FILES <<< "$VERIFY_FILES"
    for f in "${FILES[@]}"; do
        if [ -f "$TARGET_DIR/$f" ]; then
            echo "  ✅ $f"
        else
            echo "  ❌ 缺少文件: $f"
            VERIFY_OK=false
        fi
    done
fi

# 执行 postinstall 脚本（如有）
POSTINSTALL="$TARGET_DIR/scripts/postinstall-link-sdk.js"
if [ -f "$POSTINSTALL" ]; then
    echo "  执行 postinstall-link-sdk..."
    node "$POSTINSTALL" 2>&1 && echo "  ✅ plugin-sdk 链接就绪" || echo "  ⚠️  postinstall 失败，插件可能无法加载"
fi

if [ "$VERIFY_OK" != "true" ]; then
    echo ""
    echo "❌ 验证未通过"
    exit 1
fi
echo "  ✅ 验证通过"

# [3/4] 输出结构化信息
echo ""
echo "[3/4] 升级结果..."
echo "PLUGIN_NEW_VERSION=${NEW_VERSION:-unknown}"
echo "PLUGIN_ID=$PLUGIN_ID"
if [ -n "$NEW_VERSION" ]; then
    echo "PLUGIN_REPORT=✅ $PLUGIN_ID 升级完成: v${NEW_VERSION}"
else
    echo "PLUGIN_REPORT=⚠️ 升级完成但无法确认新版本"
fi

echo ""
echo "==========================================="
echo "  ✅ 安装完成"
echo "==========================================="

# [4/4] 重启 gateway
if [ "$NO_RESTART" = "true" ]; then
    echo ""
    echo "[跳过重启] --no-restart 已指定"
    exit 0
fi

echo ""
echo "[4/4] 重启 gateway..."
if $CMD gateway restart 2>&1; then
    echo "  ✅ gateway 已重启"
    [ -n "$NEW_VERSION" ] && echo "" && echo "🎉 $PLUGIN_ID 已更新至 v${NEW_VERSION}"
else
    echo "  ⚠️  gateway 重启失败，请手动执行: $CMD gateway restart"
fi


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)