Skill: fmls/web-tools-guide (version 1.0.2)

--- name: web-tools-guide description: "MANDATORY before calling web_search, web_fetch, browser, or opencli. Contains required error-handling procedures (web_search failure → must guide user to configure API), fallback chain (opencli CLI covers 70+ sites as structured fallback before browser), and site-specific login URLs. Without reading this skill, you WILL handle failures incorrectly and miss available tools. Trigger on: 搜索/上网/查资料/打开网站/抓取网页/新闻/热点/web search/fetch/browser/opencli." --- <!-- baseDir = /root/.openclaw/workspace/skills/web-tools-guide --> # Web 工具策略 遵循 ReAct 范式。**四个工具不是层级关系，是分支决策**： ``` ┌─ 没有 URL，需要搜索 ──────→ web_search （关键词搜索） │ ├─ 已知 URL，静态内容 ──────→ web_fetch （直取页面） │ ├─ 以上失败 / 不适用 ──────→ opencli （CLI 结构化访问，70+ 站点） │ └─ 全都不行 ───────────────→ browser （浏览器自动化，兜底） ``` 先按场景选 web_search 或 web_fetch；失败时先试 opencli，最后才上 browser。 每次切换工具告知用户原因，不要静默降级。 --- ## 决策流程 ``` 有明确 URL？ ├─ YES → 静态内容（文章/文档/API/RSS）？ │ ├─ YES → web_fetch │ │ 失败（空白/403/CAPTCHA）？→ opencli → browser │ └─ NO（需要 JS/登录/交互/截图）→ opencli → browser └─ NO → web_search ├─ 成功 → 对结果 URL 按上述逻辑选 fetch/opencli/browser ├─ 失败（API 错误）→ 引导配置（见"web_search 失败处理"） └─ 无结果/不适用 → opencli → browser ``` --- ## web_search **何时用**：没有明确 URL，需要搜索信息（新闻、热点、查资料、比较信息）。 **怎么用**：直接调用 `web_search`，传入搜索关键词。 **结果处理**：返回的 URL 按决策流程选 `web_fetch`、`opencli` 或 `browser` 深入获取。 **失败时**：见下方"web_search 失败处理"。 --- ## web_fetch **何时用**：已知 URL，页面为静态内容——新闻文章、博客、技术文档、API 端点、RSS 源。 **怎么用**：直接调用 `web_fetch`，传入 URL。 **失败信号**：返回空白页、403、CAPTCHA、骨架 HTML → 尝试 `opencli`，仍不行再升级到 `browser`。 --- ## opencli（Fallback，优先于 browser） **何时用**：web_search / web_fetch 失败或不适用时，先试 opencli 再考虑 browser。覆盖 70+ 主流网站，秒级返回结构化数据。 **首次使用前**：如果执行 `opencli` 提示 command not found，需要先运行安装脚本（幂等，可重复运行）： ```bash bash {baseDir}/scripts/setup-opencli.sh ``` 该脚本会自动完成：安装 opencli CLI → 编译 Browser Bridge 插件 → 重启浏览器加载插件。 **渐进式发现（不需要记命令）**： ```bash opencli --help # 有没有这个站？ opencli <site> --help # 这个站能做什么？ opencli <site> <command> --help # 这个命令怎么用？ ``` **详细用法**：`read {baseDir}/references/opencli-guide.md` **失败时**：告知用户 opencli 失败原因，降级到 browser。 --- ## browser（最后手段） 这是最重量级的工具，也是当前问题最多的场景。以下是详细操作指引。 ### 何时用 - **JS 渲染页面**：SPA、动态加载内容（微博 feed、知乎回答、小红书瀑布流） - **需要登录态**：登录后才可见的内容、管理后台 - **页面交互**：点击按钮、填写表单、翻页、滚动加载更多 - **截图需求**：需要页面视觉信息 - **其他工具全部失败的兜底** ### 操作流程 **信息获取（只读）：** 1. 导航到目标 URL 2. 等待关键元素出现（不要用固定时间等待） 3. 提取所需内容（文本、链接、图片等） 4. 返回结果给用户 **登录操作：** 1. 查找登录页 URL → `read {baseDir}/references/well-known-sites.json` 2. **告知用户即将执行登录操作，获取确认** 3. 导航到登录页 4. 填写凭证（用户提供）或提示用户扫码 5. 等待登录成功，确认后继续后续操作 **页面交互：** 1. 导航到目标页面 2. 使用 CSS 选择器定位元素（辅以文本内容匹配） 3. 执行交互：点击、输入、选择、滚动 4. 等待响应/页面变化 5. 提取结果或截图 ### 关键注意事项 - **登录操作必须获得用户授权** — 任何涉及账号登录的操作前，先告知用户并等待确认 - **敏感操作必须二次确认** — 发帖、删除、支付等不可逆操作 - **优先 CSS 选择器** — 比 XPath 更稳定，辅以文本匹配 - **智能等待** — 等待目标元素出现，而非 `sleep(3)` 式固定等待 - **CAPTCHA/验证码** — 无法自动处理时告知用户需手动介入 - **页面加载超时** — 设置合理超时，失败时告知用户并建议重试 - **多步操作保持状态** — 登录后的后续操作复用同一浏览器上下文，不要重新打开 --- ## web_search 失败处理 **当 `web_search` 返回错误时，不要静默降级，必须引导配置：** 1. **`read {baseDir}/references/web-search-config.md`** 2. 按文件中 Step 1 **原样输出**配置引导给用户（不要改写表格或省略内容） 3. 等待用户回复： - 用户提供 API Key → 再次 `read {baseDir}/references/web-search-config.md`，按 Step 2-5 执行 - 用户说"暂不配置" → 进入降级方案 - 其他回复 → 正常响应 4. **降级方案**（仅在用户明确拒绝配置后）： - `read {baseDir}/references/well-known-sites.json` 获取常用网站 URL - 用 `web_fetch` 直接获取目标网站内容 - 仍不行 → 升级到 `browser` --- ## 常用网站 需要常用网站 URL 时（登录页、搜索引擎、热搜榜等）： ``` read {baseDir}/references/well-known-sites.json ``` 通过 key 查找（如 `social.weibo.login`、`search.baidu`）。带 `{query}` 的 URL 替换为实际搜索词。

path: scripts/setup-opencli.sh
content: #!/usr/bin/env bash
# ============================================================================
# opencli 安装脚本（Web Tools Guide Skill）
# 适用于：Ubuntu 22.04+ / Debian 12+（需要已安装 OpenClaw + Node.js 18+）
# 用途：安装 opencli CLI，下载 Browser Bridge 插件，重启浏览器加载插件
# 使用：bash scripts/setup-opencli.sh
# 幂等：可重复运行，每步操作会检查当前状态，避免副作用
# ============================================================================

set -euo pipefail

# ============================================================
# 可配置变量
# ============================================================

CDP_PORT=9222                                            # Chrome DevTools Protocol 端口
OPENCLI_EXTENSION_DIR="/root/.openclaw/opencli-extension" # Browser Bridge 插件存放目录
GITHUB_RELEASE_BASE="https://github.com/jackwener/opencli/releases" # GitHub Releases 地址

# ============================================================
# 辅助函数
# ============================================================

info()  { echo "[INFO] $*"; }
ok()    { echo "[ OK ] $*"; }
warn()  { echo "[WARN] $*"; }
fail()  { echo "[FAIL] $*"; exit 1; }

has_cmd() { command -v "$1" >/dev/null 2>&1; }

# 加载 Node.js 环境（NVM + pnpm）
# 非交互式 shell 不会自动加载 .bashrc，需要手动 source
load_node_env() {
    export NVM_DIR="${HOME}/.nvm"
    if [ -s "${NVM_DIR}/nvm.sh" ]; then
        . "${NVM_DIR}/nvm.sh"
    fi
    export PNPM_HOME="${HOME}/.local/share/pnpm"
    export PATH="${PNPM_HOME}:${PATH}"
}

# 检测 CDP 端口是否正常响应
is_cdp_alive() {
    curl -s --max-time 2 "http://localhost:${CDP_PORT}/json/version" &>/dev/null
}

# ============================================================
# 前置检查
# ============================================================

preflight() {
    info "前置检查..."

    [[ "$(uname -s)" == "Linux" ]] || fail "仅支持 Linux（当前: $(uname -s)）"
    has_cmd node                   || fail "未找到 node — 请先安装 Node.js 18+"
    has_cmd npm                    || fail "未找到 npm — 请先安装 Node.js 18+"

    ok "前置检查通过"
}

# ============================================================
# 第一步: 安装 opencli CLI（全局）
# ============================================================

install_opencli_cli() {
    info "检查 opencli CLI..."

    if has_cmd opencli; then
        ok "opencli CLI 已安装: $(which opencli)"
        return 0
    fi

    info "安装 opencli CLI..."
    npm install -g @jackwener/opencli

    if ! has_cmd opencli; then
        fail "opencli 安装失败"
    fi

    ok "opencli CLI 安装完成"
}

# ============================================================
# 第二步: 下载 Browser Bridge 插件（从 GitHub Releases）
# ============================================================

download_browser_bridge() {
    info "检查 Browser Bridge 插件..."

    local ext_dir="${OPENCLI_EXTENSION_DIR}"

    # 已存在 manifest.json 说明之前下载过，跳过
    if [ -f "${ext_dir}/manifest.json" ]; then
        ok "Browser Bridge 插件已存在: ${ext_dir}"
        return 0
    fi

    has_cmd curl || fail "未找到 curl — 无法下载 Browser Bridge 插件"
    has_cmd unzip || fail "未找到 unzip — 无法解压 Browser Bridge 插件"

    # 获取 opencli 版本号，用于拼接 release 下载地址
    local version
    version=$(opencli --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) || true
    if [ -z "$version" ]; then
        warn "无法获取 opencli 版本号，使用 latest release"
    fi

    # 优先按版本下载，失败则回退到 latest
    local download_url
    local tmp_zip
    tmp_zip=$(mktemp /tmp/opencli-extension-XXXXXX.zip)

    local downloaded=false
    if [ -n "$version" ]; then
        download_url="${GITHUB_RELEASE_BASE}/download/v${version}/opencli-extension.zip"
        info "尝试下载 Browser Bridge 插件 v${version}..."
        if curl -fsSL --max-time 60 -o "$tmp_zip" "$download_url" 2>/dev/null; then
            downloaded=true
        else
            warn "v${version} 下载失败，回退到 latest release"
        fi
    fi

    if [ "$downloaded" = false ]; then
        # 通过 GitHub API 获取 latest release 的 opencli-extension.zip 下载地址
        download_url="${GITHUB_RELEASE_BASE}/latest/download/opencli-extension.zip"
        info "下载 Browser Bridge 插件（latest release）..."
        if ! curl -fsSL --max-time 60 -o "$tmp_zip" "$download_url" 2>/dev/null; then
            rm -f "$tmp_zip"
            fail "Browser Bridge 插件下载失败，请检查网络或手动下载: ${GITHUB_RELEASE_BASE}"
        fi
    fi

    # 解压到目标目录
    info "解压 Browser Bridge 插件到 ${ext_dir}..."
    mkdir -p "$ext_dir"
    unzip -qo "$tmp_zip" -d "$ext_dir"
    rm -f "$tmp_zip"

    # 验证解压结果（manifest.json 是 Chrome 扩展的必备文件）
    if [ ! -f "${ext_dir}/manifest.json" ]; then
        # 可能 zip 内有一层子目录，尝试提升
        local sub_dir
        sub_dir=$(find "$ext_dir" -maxdepth 1 -mindepth 1 -type d | head -1)
        if [ -n "$sub_dir" ] && [ -f "${sub_dir}/manifest.json" ]; then
            # 将子目录内容移到 ext_dir
            mv "${sub_dir}"/* "$ext_dir/" 2>/dev/null || true
            mv "${sub_dir}"/.[!.]* "$ext_dir/" 2>/dev/null || true
            rmdir "$sub_dir" 2>/dev/null || true
        fi
    fi

    if [ ! -f "${ext_dir}/manifest.json" ]; then
        fail "Browser Bridge 插件解压异常：未找到 manifest.json"
    fi

    ok "Browser Bridge 插件下载完成: ${ext_dir}"
}

# 获取 opencli extension 路径
get_opencli_extension_dir() {
    local ext_dir="${OPENCLI_EXTENSION_DIR}"
    if [ -d "$ext_dir" ] && [ -f "${ext_dir}/manifest.json" ]; then
        echo "$ext_dir"
    fi
}

# ============================================================
# 第三步: 重启浏览器加载 opencli 插件
# 从 /proc 提取当前 Chrome 命令行，杀掉进程，追加 extension 参数后重新启动
# ============================================================

# 从 /proc/<pid>/cmdline 提取 Chrome 进程的完整命令行
# 返回以空格分隔的参数列表
get_chrome_cmdline_by_pid() {
    local pid="$1"
    if [ -f "/proc/${pid}/cmdline" ]; then
        # cmdline 以 NUL 分隔，转为空格
        tr '\0' ' ' < "/proc/${pid}/cmdline" | sed 's/ *$//'
    fi
}

# 查找正在监听 CDP_PORT 的 Chrome 主进程 PID
# 返回 PID 或空字符串
find_chrome_pid_by_cdp_port() {
    local pid
    # 方法1: 通过 ss/netstat 查找监听端口的进程
    if has_cmd ss; then
        pid=$(ss -tlnp "sport = :${CDP_PORT}" 2>/dev/null \
            | grep -oP 'pid=\K[0-9]+' | head -1) || true
    fi
    # 方法2: 通过 lsof
    if [ -z "${pid:-}" ] && has_cmd lsof; then
        pid=$(lsof -ti :${CDP_PORT} -sTCP:LISTEN 2>/dev/null | head -1) || true
    fi
    # 方法3: 通过 /proc 遍历查找含 remote-debugging-port 的 chrome 进程
    if [ -z "${pid:-}" ]; then
        pid=$(ps aux | grep '[c]hrome.*--remote-debugging-port='"${CDP_PORT}" \
            | awk '{print $2}' | head -1) || true
    fi
    echo "${pid:-}"
}

# 从 /proc 提取当前 Chrome 命令行，杀掉进程，追加 extension 参数后重新启动
reload_browser_process() {
    local ext_dir="$1"

    local chrome_pid
    chrome_pid=$(find_chrome_pid_by_cdp_port)
    if [ -z "$chrome_pid" ]; then
        warn "CDP 端口 ${CDP_PORT} 无响应，且未找到 Chrome 进程"
        warn "跳过浏览器插件加载（opencli CLI 仍可使用，但 Browser Bridge 不可用）"
        return 0
    fi

    info "找到 Chrome 进程 PID: ${chrome_pid}"

    # 检查该进程是否已加载了 extension
    local current_cmdline
    current_cmdline=$(get_chrome_cmdline_by_pid "$chrome_pid")
    if echo "$current_cmdline" | grep -q "load-extension=.*${ext_dir}"; then
        ok "浏览器已加载 opencli 插件，无需重启"
        return 0
    fi

    # 提取原始命令行，准备追加 extension 参数
    if [ -z "$current_cmdline" ]; then
        warn "无法读取 Chrome 进程命令行（/proc/${chrome_pid}/cmdline）"
        warn "跳过浏览器插件加载"
        return 0
    fi

    info "提取 Chrome 原始启动命令..."

    # 移除命令行中可能已有的旧 extension 参数
    local clean_cmdline
    clean_cmdline=$(echo "$current_cmdline" \
        | sed 's/--disable-extensions-except=[^ ]* *//g' \
        | sed 's/--load-extension=[^ ]* *//g')

    # 追加 extension 参数
    local new_cmdline="${clean_cmdline} --disable-extensions-except=${ext_dir} --load-extension=${ext_dir}"

    info "终止当前 Chrome 进程 (PID: ${chrome_pid})..."
    kill "$chrome_pid" 2>/dev/null || true
    # 等待进程退出（最多 5 秒）
    local wait_count=10
    while [ $wait_count -gt 0 ] && kill -0 "$chrome_pid" 2>/dev/null; do
        sleep 0.5
        wait_count=$((wait_count - 1))
    done
    # 如果还没退出，强制杀掉
    if kill -0 "$chrome_pid" 2>/dev/null; then
        warn "Chrome 进程未响应 SIGTERM，发送 SIGKILL..."
        kill -9 "$chrome_pid" 2>/dev/null || true
        sleep 1
    fi

    info "使用新参数重新启动 Chrome..."
    # 后台启动，重定向输出到日志文件
    local log_file="/tmp/opencli-chrome-restart.log"
    nohup bash -c "exec ${new_cmdline}" > "$log_file" 2>&1 &
    local new_pid=$!
    info "Chrome 已启动 (PID: ${new_pid})，日志: ${log_file}"

    wait_cdp_ready "cat ${log_file}"
}

# 等待 CDP 端口就绪（最多 15 秒）
wait_cdp_ready() {
    local debug_hint="${1:-}"
    local retries=30
    while [ $retries -gt 0 ]; do
        if is_cdp_alive; then
            ok "浏览器重启成功，CDP 端口 ${CDP_PORT} 就绪，opencli 插件已加载"
            return 0
        fi
        sleep 0.5
        retries=$((retries - 1))
    done
    if [ -n "$debug_hint" ]; then
        fail "浏览器重启超时，查看日志: ${debug_hint}"
    else
        fail "浏览器重启超时"
    fi
}

# 主入口: 查找运行中的 Chrome 进程，杀掉后带 extension 参数重启
reload_browser_with_extension() {
    info "检查浏览器是否已加载 opencli 插件..."

    local ext_dir
    ext_dir=$(get_opencli_extension_dir)
    if [ -z "$ext_dir" ]; then
        fail "未找到 opencli extension，请先运行安装和下载步骤"
    fi

    # 检查 CDP 端口是否存活
    if is_cdp_alive; then
        reload_browser_process "$ext_dir"
        return $?
    fi

    # CDP 端口无响应
    warn "CDP 端口 ${CDP_PORT} 无响应，未检测到运行中的浏览器"
    warn "请先启动浏览器（带 --remote-debugging-port=${CDP_PORT} 参数）"
    warn "跳过浏览器插件加载（opencli CLI 仍可使用，但 Browser Bridge 不可用）"
    return 0
}

# ============================================================
# 验证
# ============================================================

verify() {
    info "验证 opencli 状态..."

    if ! has_cmd opencli; then
        fail "opencli 未安装"
    fi

    local doctor_output
    doctor_output=$(opencli doctor 2>&1) || true

    if echo "$doctor_output" | grep -q "Extension.*OK"; then
        ok "opencli doctor: 全部正常"
    else
        warn "opencli doctor 存在异常:"
        echo "$doctor_output" | head -10 | sed 's/^/  /'
        echo ""
        warn "Browser Bridge 可能未就绪（opencli CLI 命令仍可使用）"
        warn "如果浏览器刚重启，请等待几秒后重试: opencli doctor"
    fi
}

# ============================================================
# 主流程
# ============================================================

main() {
    echo ""
    echo "=== opencli 安装（Web Tools Guide Skill） ==="
    echo ""

    load_node_env
    preflight

    echo ""
    install_opencli_cli           # 第一步: 安装 opencli CLI

    echo ""
    download_browser_bridge       # 第二步: 下载 Browser Bridge 插件（从 GitHub Releases）

    echo ""
    reload_browser_with_extension # 第三步: 重启浏览器加载插件

    echo ""
    verify                        # 验证

    echo ""
    ok "opencli 安装完成！可通过 'opencli --help' 查看可用命令"
    echo ""
}

main "$@"


path: references/opencli-guide.md
content: # opencli 使用指引

`opencli` 是一个命令行工具，将 70+ 主流网站封装为 CLI 命令。相比 browser 自动化，它更快（秒级）、更精准（结构化数据）、更稳定（不依赖 DOM 变化）。

## 核心理念

不需要记住任何命令。通过 `--help` 逐层探索即可：

```bash
opencli --help                         # 我能操作哪些网站？
opencli <site> --help                  # 这个网站有哪些操作？
opencli <site> <command> --help        # 这个操作怎么用？
```

## 典型场景映射

以下是常见用户需求到 opencli 命令的映射示例（不完整，遇到新场景先 `--help` 探索）：

| 用户需求 | opencli 命令 |
|---------|-------------|
| 看微博热搜 | `opencli weibo hot` |
| 搜索小红书笔记 | `opencli xiaohongshu search <关键词>` |
| 看知乎热榜 | `opencli zhihu hot` |
| B站热门视频 | `opencli bilibili hot` |
| 搜索 Twitter | `opencli twitter search <关键词>` |
| 看 HackerNews 头条 | `opencli hackernews top` |
| GitHub trending | `opencli gh trending` |
| 获取笔记详情/评论 | `opencli xiaohongshu note <note-id>` |
| 获取 YouTube 字幕 | `opencli youtube transcript <video-id>` |
| 读取任意网页为 Markdown | `opencli web read --url <URL>` |

## 输出格式

所有命令支持 `-f` / `--format` 选项：

| 格式 | 用途 |
|------|------|
| `table` | 默认，终端可读 |
| `json` | 程序化处理、需要完整结构化数据 |
| `yaml` | 人类可读的结构化数据 |
| `md` | Markdown 格式 |
| `csv` | 表格导出 |
| `plain` | 纯文本 |

推荐：给用户展示时用 `table` 或 `md`，需要后续处理时用 `-f json`。

## 常见参数

- `--limit <N>`：限制返回条数
- `-f <format>`：输出格式
- `-v` / `--verbose`：调试输出

## 覆盖的网站类别

以下是 opencli 覆盖的主要网站类别（可通过 `opencli --help` 查看完整列表）：

- **中文社交**：微博、小红书、知乎、豆瓣、即刻、V2EX、贴吧
- **中文视频**：B站、抖音
- **中文资讯**：36氪、雪球、什么值得买
- **阅读**：微信读书、知识星球
- **国际社交**：Twitter/X、Reddit、LinkedIn、Instagram、Facebook、Bluesky
- **国际视频**：YouTube、TikTok
- **国际资讯**：HackerNews、ProductHunt、Medium、Substack、BBC、Bloomberg、Reuters
- **开发者**：GitHub (gh)、StackOverflow、Dev.to、ArXiv、HuggingFace
- **AI 工具**：ChatGPT、Gemini、Grok、Doubao
- **购物**：JD、Amazon、Coupang、Steam
- **播客**：Apple Podcasts、小宇宙、Spotify
- **通用**：`opencli web read` — 任意网页转 Markdown

## 写操作注意

部分命令支持写操作（发帖、关注、点赞等），执行前必须告知用户并获取确认：

- `opencli twitter post <text>` — 发推文
- `opencli xiaohongshu publish <content>` — 发小红书笔记
- `opencli twitter follow/block/like` — 关注/拉黑/点赞
- 其他涉及修改数据的操作

## 故障排查

如果 opencli 命令执行失败：

1. **检查 daemon 状态**：`opencli doctor` — 诊断浏览器桥接连接
2. **查看详细错误**：加 `-v` 参数获取调试信息
3. **降级处理**：告知用户 opencli 失败原因，降级到 web_search/web_fetch/browser


path: references/well-known-sites.json
content: {
  "_description": "常用网站地址索引 — 供 Agent 快速定位目标地址，按类别组织。key 为语义化标识，value 包含 url / description / category。",

  "search.baidu": {
    "url": "https://www.baidu.com/s?wd={query}",
    "description": "百度搜索（中文首选），{query} 替换为搜索关键词",
    "category": "search_engine"
  },
  "search.google": {
    "url": "https://www.google.com/search?q={query}",
    "description": "Google 搜索（英文/国际首选），{query} 替换为搜索关键词",
    "category": "search_engine"
  },
  "search.bing": {
    "url": "https://www.bing.com/search?q={query}",
    "description": "Bing 搜索（中英文均可），{query} 替换为搜索关键词",
    "category": "search_engine"
  },
  "search.duckduckgo": {
    "url": "https://duckduckgo.com/?q={query}",
    "description": "DuckDuckGo 搜索（隐私友好），{query} 替换为搜索关键词",
    "category": "search_engine"
  },
  "search.sogou": {
    "url": "https://www.sogou.com/web?query={query}",
    "description": "搜狗搜索，{query} 替换为搜索关键词",
    "category": "search_engine"
  },

  "social.weibo.login": {
    "url": "https://passport.weibo.com/sso/signin?entry=miniblog&source=miniblog&disp=popup&url=https%3A%2F%2Fweibo.com%2Fnewlogin%3Ftabtype%3Dweibo%26gid%3D102803%26openLoginLayer%3D0%26url%3Dhttps%3A%2F%2Fweibo.com%2F&from=weibopro",
    "description": "微博登录页面（SSO 单点登录入口）,注意在此页面截图时不要使用全页截图，fullpage:false",
    "category": "social_platform"
  },
  "social.weibo.home": {
    "url": "https://weibo.com/",
    "description": "微博首页",
    "category": "social_platform"
  },
  "social.weibo.hot": {
    "url": "https://s.weibo.com/top/summary",
    "description": "微博热搜榜",
    "category": "social_platform"
  },
  "social.zhihu.home": {
    "url": "https://www.zhihu.com/",
    "description": "知乎首页",
    "category": "social_platform"
  },
  "social.zhihu.hot": {
    "url": "https://www.zhihu.com/hot",
    "description": "知乎热榜",
    "category": "social_platform"
  },
  "social.zhihu.search": {
    "url": "https://www.zhihu.com/search?type=content&q={query}",
    "description": "知乎搜索，{query} 替换为搜索关键词",
    "category": "social_platform"
  },
  "social.douyin.home": {
    "url": "https://www.douyin.com/",
    "description": "抖音首页（Web 版）",
    "category": "social_platform"
  },
  "social.xiaohongshu.home": {
    "url": "https://www.xiaohongshu.com/",
    "description": "小红书首页（Web 版）",
    "category": "social_platform"
  },
  "social.twitter.home": {
    "url": "https://x.com/",
    "description": "X (Twitter) 首页",
    "category": "social_platform"
  },

  "video.bilibili.home": {
    "url": "https://www.bilibili.com/",
    "description": "哔哩哔哩首页",
    "category": "video_platform"
  },
  "video.bilibili.hot": {
    "url": "https://www.bilibili.com/v/popular/rank/all",
    "description": "哔哩哔哩全站排行榜",
    "category": "video_platform"
  },
  "video.bilibili.search": {
    "url": "https://search.bilibili.com/all?keyword={query}",
    "description": "哔哩哔哩搜索，{query} 替换为搜索关键词",
    "category": "video_platform"
  },
  "video.youtube.home": {
    "url": "https://www.youtube.com/",
    "description": "YouTube 首页",
    "category": "video_platform"
  },

  "news.toutiao": {
    "url": "https://www.toutiao.com/",
    "description": "今日头条首页",
    "category": "news"
  },
  "news.36kr": {
    "url": "https://36kr.com/",
    "description": "36氪首页（科技/创投资讯）",
    "category": "news"
  },
  "news.hackernews": {
    "url": "https://news.ycombinator.com/",
    "description": "Hacker News 首页（技术/创业资讯）",
    "category": "news"
  },
  "news.sspai": {
    "url": "https://sspai.com/",
    "description": "少数派首页（效率/数码资讯）",
    "category": "news"
  },

  "dev.github.home": {
    "url": "https://github.com/",
    "description": "GitHub 首页",
    "category": "dev_tool"
  },
  "dev.github.trending": {
    "url": "https://github.com/trending",
    "description": "GitHub Trending（热门项目）",
    "category": "dev_tool"
  },
  "dev.github.search": {
    "url": "https://github.com/search?q={query}&type=repositories",
    "description": "GitHub 仓库搜索，{query} 替换为搜索关键词",
    "category": "dev_tool"
  },
  "dev.npm.search": {
    "url": "https://www.npmjs.com/search?q={query}",
    "description": "npm 包搜索，{query} 替换为包名/关键词",
    "category": "dev_tool"
  },
  "dev.pypi.search": {
    "url": "https://pypi.org/search/?q={query}",
    "description": "PyPI 包搜索，{query} 替换为包名/关键词",
    "category": "dev_tool"
  },
  "dev.stackoverflow": {
    "url": "https://stackoverflow.com/search?q={query}",
    "description": "Stack Overflow 搜索，{query} 替换为问题关键词",
    "category": "dev_tool"
  },
  "dev.mdn": {
    "url": "https://developer.mozilla.org/zh-CN/search?q={query}",
    "description": "MDN Web Docs 搜索（中文版），{query} 替换为关键词",
    "category": "dev_tool"
  },

  "wechat.mp.login": {
    "url": "https://mp.weixin.qq.com/",
    "description": "微信公众平台登录页",
    "category": "wechat"
  },
  "wechat.mp.admin": {
    "url": "https://mp.weixin.qq.com/cgi-bin/home?t=home/index",
    "description": "微信公众平台管理后台首页（需登录态）",
    "category": "wechat"
  },

  "ai.chatgpt": {
    "url": "https://chatgpt.com/",
    "description": "ChatGPT 首页",
    "category": "ai_tool"
  },
  "ai.claude": {
    "url": "https://claude.ai/",
    "description": "Claude AI 首页",
    "category": "ai_tool"
  },
  "ai.huggingface": {
    "url": "https://huggingface.co/",
    "description": "Hugging Face 首页（AI 模型/数据集平台）",
    "category": "ai_tool"
  },

  "doc.wikipedia.zh": {
    "url": "https://zh.wikipedia.org/wiki/{query}",
    "description": "中文维基百科条目，{query} 替换为条目名",
    "category": "reference"
  },
  "doc.wikipedia.en": {
    "url": "https://en.wikipedia.org/wiki/{query}",
    "description": "英文维基百科条目，{query} 替换为条目名",
    "category": "reference"
  },
  "doc.baike.baidu": {
    "url": "https://baike.baidu.com/item/{query}",
    "description": "百度百科条目，{query} 替换为条目名",
    "category": "reference"
  },

  "cloud.tencent.login": {
    "url": "https://cloud.tencent.com/login",
    "description": "腾讯云登录页",
    "category": "cloud_provider"
  },
  "cloud.tencent.console": {
    "url": "https://cloud.tencent.com/",
    "description": "腾讯云控制台首页",
    "category": "cloud_provider"
  },
  "cloud.aliyun.login": {
    "url": "https://account.aliyun.com/",
    "description": "阿里云登录页",
    "category": "cloud_provider"
  },
  "cloud.aliyun.console": {
    "url": "https://www.aliyun.com/",
    "description": "阿里云控制台首页",
    "category": "cloud_provider"
  },
  "cloud.aws.login": {
    "url": "https://signin.aws.amazon.com/",
    "description": "AWS 登录页",
    "category": "cloud_provider"
  },
  "cloud.aws.console": {
    "url": "https://console.aws.amazon.com/",
    "description": "AWS 控制台首页",
    "category": "cloud_provider"
  },
  "cloud.huawei.login": {
    "url": "https://auth.huaweicloud.com/",
    "description": "华为云登录页",
    "category": "cloud_provider"
  },
  "cloud.huawei.console": {
    "url": "https://www.huaweicloud.com/",
    "description": "华为云控制台首页",
    "category": "cloud_provider"
  },
  "cloud.google.login": {
    "url": "https://console.cloud.google.com/",
    "description": "Google Cloud 登录页/控制台",
    "category": "cloud_provider"
  },
  "cloud.azure.login": {
    "url": "https://portal.azure.com/",
    "description": "Microsoft Azure 登录页/控制台",
    "category": "cloud_provider"
  }
}


path: references/web-search-config.md
content: # Web Search API 配置流程

当 `web_search` 返回错误后，Agent 读取此文件并按步骤执行。

---

## Step 1: 初始引导（检测到未配置状态）

**⚠️ 关键约束**：停止 fallback 链。将下方引用块（`>` 开头的部分）**原样输出**给用户。不要重新组织、改写表格结构、或编造额外信息（如"无需绑定信用卡"）。直接复制粘贴：

> 搜索功能需要配置 API Key 才能使用。
>
> **推荐方案**（二选一）：
> - **Tavily**: https://app.tavily.com — 国际内容效果好，每月 1000 免费积分
> - **Kimi**: https://platform.moonshot.cn — 中文内容效果佳
>
> **计费对比**：
>
> | 对比维度 | Tavily | Kimi |
> |---------|--------|------|
> | **免费额度** | 每月 1000 次基础搜索免费 | 无免费额度，每次≈0.03元 |
> | **计费模式** | 失败不收费 | 固定0.03元/次+Token费 |
>
> 请提供您的 API Key（格式：`tvly-xxx...` 或 `sk-xxx...`），或回复"暂不配置"使用备选方案。

**如果用户选择"暂不配置"：**
1. 回到 SKILL.md 的降级决策流程（使用 web_fetch 配合搜索引擎 URL，或 browser）
2. 告知用户："已切换到备选方案。如需更好体验，可随时配置 API Key。"

---

## Step 2: 接收 API Key 并分析

**⚠️ 收到用户的 Key 后，必须先执行本步骤。禁止跳过分析和确认直接进入 Step 3 配置。**

**当用户提供 API Key 时：**

1. **通过前缀自动检测提供商**：
   - `tvly-*` → Tavily
   - `sk-*` → Kimi
   - 未知前缀 → 询问用户使用哪个提供商

2. **分析并告知用户**（先不配置）：
   - 如果 `tvly-*`："检测到您提供的是 **Tavily** 的 API Key ✅"
   - 如果 `sk-*`："检测到您提供的是 **Kimi** 的 API Key ✅"

3. **在操作前请求确认**（原样输出以下内容）：
   > 接下来我将进行以下操作：
   > 1. 配置 API Key 到 openclaw.json
   > 2. 重启 gateway 以应用配置
   >
   > ⚠️ **重要提示**：
   > - 稍待片刻，请主动发送消息（如"继续"）唤醒会话
   > - 我将继续为您执行搜索
   >
   > 请回复"确认"开始配置，或回复"取消"放弃配置。

4. **等待用户确认** — 用户回复前不要继续操作

---

## Step 3: 执行配置（用户确认后）

**仅在用户确认后执行（如"确认"、"好"、"开始"）：**

1. **配置 openclaw.json**：
   ```bash
   # Tavily（tvly-* 前缀）
   openclaw config set plugins.entries.tavily.enabled true
   openclaw config set plugins.entries.tavily.config.webSearch.apiKey <user-provided-key>

   # Kimi（sk-* 前缀）
   openclaw config set plugins.entries.kimi.enabled true
   openclaw config set plugins.entries.kimi.config.webSearch.apiKey <user-provided-key>
   ```

2. **告知用户配置完成**：
   > ✅ API Key 已保存！

---

## Step 4: 重启 Gateway

1. **执行重启**：
   ```bash
   # 重启 gateway 以应用配置变更
   openclaw restart gateway
   # 或使用 Docker
   docker restart openclaw-gateway
   # 或查找并重启 gateway 进程
   pkill -f "openclaw-gateway" || pkill -f "gateway"
   ```

2. **告知用户等待并唤醒会话**：
   > 正在重启 gateway 以应用配置...
   >
   > ⏳ 稍待片刻，请发送任意消息（如"继续"）唤醒会话，我将继续为您搜索。

---

## Step 5: 等待用户唤醒会话

**停止并等待用户消息。**

当用户发送任意消息（如"继续"、"已重启"、"好了"、"在吗"）后：

1. **验证 API Key 是否配置成功**：
   ```bash
   # 检查 Tavily
   openclaw config get plugins.entries.tavily.config.webSearch.apiKey
   # 检查 Kimi
   openclaw config get plugins.entries.kimi.config.webSearch.apiKey
   ```

2. 如果已配置 → 执行用户原始搜索请求（使用 `web_search` 工具）
3. 如果未配置 → 重新进入配置流程

---

## 快速参考

| 提供商 | 网站 | Key 前缀 | 中文支持 |
|--------|------|---------|---------|
| Tavily | https://app.tavily.com | `tvly-` | 良好 |
| Kimi/Moonshot | https://platform.moonshot.cn | `sk-` | 优秀 |

**自动检测规则**：系统通过 Key 前缀自动识别提供商。


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