Files
u-claw/install/install.sh
zheng b076815171
Some checks failed
Tests / test (push) Has been cancelled
feat: 海外化改造(新加坡市场)—— 阶段 0-3
按 U-Claw-海外化改造方案.md 与 范围决策记录.md 实施。这是 fork,不回上游:
海外版删掉的正是上游的中国市场默认值。

阶段 0 地基
- 下载源全部改国际:脚本/CI 61 处 + lockfile 880 条 npmmirror URL 归零
  (lockfile 那 880 条是 npm 的 resolved 字段,脚本层参数化根本绕不过它)
- 移除 install.ps1 里三个第三方 GitHub 加速代理,bundle 改直连 + SHA256 校验
  (原来只检查"文件大于 1MB"就解压运行)
- 技能内容与分发分离:skills/manifest.json 单一来源,install.sh 1170→658 行、
  install.ps1 721→546 行,两者技能内容归零
  实测原来是三份不一致:skills-cn 完整、install.sh 约 40%、install.ps1 约 17%,
  且 7 个通用技能只有 U 盘版有 —— 一键安装的用户一个能用的技能都没有
- Node 版本三种(v22.14/16/22.1)统一,新建 NODE_VERSION 单一来源
- Config 页三份合一。portable/Config.html 用根相对路径调 API 却只从 file:// 打开,
  保存功能已静默失效两个月;现缩为 120 行重定向壳
- 测试接入 CI(此前 node --test 无人运行,所有断言形同虚设)

阶段 1 双语可用
- 浏览器侧 i18n:JSON 为源、生成经典 script(file:// 下 fetch 本地 JSON 被拦)
  语言跟盘走不跟机器走:启动器写 data/.openclaw/locale.js
- 8 处硬编码 lang="zh-CN" 归零,data-i18n 覆盖 213 处,词条 en/zh 各 279 条
- B3 单框 Key:12 张模型卡 → 一个输入框,前缀识别 provider,
  服务端 /api/test-key 发 1-token 请求实测,错误映射成人话
  Key 填错到得知:从"直到对话失败"降到 ≤1 秒
- 区域格式 SG:DD/MM/YYYY、12 小时、S$、Asia/Singapore
  (ICU 在 en-SG 下把 SGD 渲染成裸 $,与美元无法区分,故自行拼 S$)
- README 内容分叉而非翻译,§1.3 证据清单逐条清零

阶段 2 降门槛
- 启动逻辑上移 lib/start.mjs:Windows-Start.bat 220→28 行、
  Mac-Start.command 235→33 行
  修掉 Mac 侧两个 bug:控制台端口硬编码 18788(回落时打开死页)、
  微信插件从未在 Mac 上安装
- U 盘根目录 23 → 3 个可点文件,其余进 advanced/
- 首启向导:语言 → 用途(7 角色,manifest 驱动)→ 密钥,答过不再问
- 三档界面,Simple 档隐藏一切技术名词
- 自动自愈:启动失败先自查自修,修不好导出脱敏诊断包
  (Doctor 从"用户要知道去点的工具"变成后台机制)

阶段 3 技能库
- 19 个英文技能,planned 归零。sg-weather / sg-transport 的端点均实测过
- SkillHub 从 56 张手写第三方卡片改为 manifest 生成:703→125 行,中文归零

其他
- origin.json 收拢所有运行时地址,tests/origin.test.mjs 保证迁移不会漏
- portable/ 下用户可见中文归零(由断言保证)
- 82 项测试

未验证(本机无 Windows / 无 pwsh):
- install.ps1、setup.ps1 约 210 行改动从未经 PowerShell 解析器
- 完整启动路径仅在假 node + 假 openclaw 上冒烟
- 8 个 .bat 的盘根推导仅静态断言
详见 U盘实测清单.md

受阻:
- 隐藏黑窗口 —— 需代码签名证书(.vbs 已被 Windows 弃用,替代方案都要签名)
- 场景卡 —— OpenClaw 上游 Dashboard 无预填 prompt 接口
- 官网 36 条 —— 上游 2026-04-14 拆到私有仓库,无权限
2026-08-17 18:37:49 +08:00

633 lines
21 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# ============================================================
# U-Claw 一键安装脚本 (Mac/Linux)
# 用法: curl -fsSL https://u-claw.org/install.sh | bash
# 或: bash install.sh
# ============================================================
set -e
set -o pipefail
# 注意:未启用 `set -u` —— 第 5 步交互式分支会有意把 API_KEY/BASE_URL/KEY_LABEL 等未赋值的变量
# 用 [-z "$X"] 检测后再写配置;启用 -u 会破坏这条降级路径。改用显式 `: "${VAR:=}"` 默认值过于侵入。
# ---- 颜色定义 ----
GREEN='\033[0;32m'
CYAN='\033[0;36m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
# ---- 常量 ----
UCLAW_DIR="$HOME/.uclaw"
RUNTIME_DIR="$UCLAW_DIR/runtime"
CORE_DIR="$UCLAW_DIR/core"
DATA_DIR="$UCLAW_DIR/data"
CONFIG_PATH="$DATA_DIR/.openclaw/openclaw.json"
NODE_VERSION="v22.22.1"
MIRROR="https://registry.npmjs.org"
NODE_MIRROR="https://nodejs.org/dist"
OPENCLAW_VERSION="2026.7.1-2"
# Skills: content lives in skills/, described by skills/manifest.json.
# UCLAW_REF pins which revision curl|bash pulls skill content from.
UCLAW_LOCALE="${UCLAW_LOCALE:-en}"
UCLAW_REF="${UCLAW_REF:-main}"
SKILL_INSTALLER_URL="https://gitea.fanghe.it.com/zhenghy/u-claw/raw/branch/$UCLAW_REF/lib/install-skills.mjs"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || echo "")"
# ============================================================
# Step 1: Banner + 系统检测
# ============================================================
clear 2>/dev/null || true
echo ""
echo -e "${CYAN}${BOLD}"
cat << 'BANNER'
╔══════════════════════════════════════════╗
║ U-Claw installer ║
║ One command. One AI assistant. ║
╚══════════════════════════════════════════╝
BANNER
echo -e "${NC}"
# 系统检测
OS=$(uname -s)
ARCH=$(uname -m)
if [ "$OS" = "Darwin" ]; then
if [ "$ARCH" = "arm64" ]; then
PLATFORM="darwin-arm64"
NODE_DIR="node-mac-arm64"
echo -e " System: ${GREEN}macOS Apple Silicon ✓${NC}"
else
PLATFORM="darwin-x64"
NODE_DIR="node-mac-x64"
echo -e " System: ${GREEN}macOS Intel ✓${NC}"
fi
elif [ "$OS" = "Linux" ]; then
if [ "$ARCH" = "x86_64" ]; then
PLATFORM="linux-x64"
NODE_DIR="node-linux-x64"
echo -e " System: ${GREEN}Linux x64 ✓${NC}"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
PLATFORM="linux-arm64"
NODE_DIR="node-linux-arm64"
echo -e " System: ${GREEN}Linux ARM64 ✓${NC}"
else
echo -e " ${RED}U-Claw does not have a build for this processor ($ARCH).${NC}"
exit 1
fi
else
echo -e " ${RED}U-Claw does not have a build for this operating system ($OS).${NC}"
echo -e " ${YELLOW}On Windows, run this in PowerShell instead: irm https://u-claw.org/install.ps1 | iex${NC}"
exit 1
fi
echo -e " Installing to: ${CYAN}$UCLAW_DIR${NC}"
echo ""
# 检查已有安装
if [ -d "$UCLAW_DIR/core/node_modules/openclaw" ]; then
echo -e " ${YELLOW}U-Claw is already installed at $UCLAW_DIR${NC}"
if [ -t 0 ]; then
read -p " Reinstall over it? (y/n) [y]: " -n 1 OVERWRITE
echo ""
if [ "$OVERWRITE" = "n" ] || [ "$OVERWRITE" = "N" ]; then
echo -e " ${DIM}Cancelled.${NC}"
exit 0
fi
else
echo -e " ${CYAN}Running non-interactively — reinstalling.${NC}"
fi
echo ""
fi
# 创建目录结构
mkdir -p "$RUNTIME_DIR" "$CORE_DIR" "$DATA_DIR/.openclaw" "$DATA_DIR/memory" "$DATA_DIR/backups"
# ============================================================
# Step 2: Node.js v22 安装
# ============================================================
echo -e " ${BOLD}[1/7] Installing Node.js $NODE_VERSION${NC}"
NODE_INSTALL_DIR="$RUNTIME_DIR/$NODE_DIR"
INSTALL_NODE=""
INSTALL_NPM=""
# 检查系统 Node.js
USE_SYSTEM_NODE=false
if command -v node >/dev/null 2>&1; then
SYS_VER=$(node --version)
MAJOR=$(echo "$SYS_VER" | sed 's/v//' | cut -d. -f1)
if [ "$MAJOR" -ge 20 ] 2>/dev/null; then
echo -e " ${GREEN}${NC} Using the Node.js already on this machine ($SYS_VER)"
INSTALL_NODE="$(which node)"
INSTALL_NPM="$(which npm)"
USE_SYSTEM_NODE=true
fi
fi
if [ "$USE_SYSTEM_NODE" = "false" ]; then
if [ -f "$NODE_INSTALL_DIR/bin/node" ]; then
echo -e " ${GREEN}${NC} Node.js already downloaded"
INSTALL_NODE="$NODE_INSTALL_DIR/bin/node"
INSTALL_NPM="$NODE_INSTALL_DIR/bin/npm-cli.js"
else
echo -e " ${CYAN}${NC} Downloading Node.js $NODE_VERSION ($PLATFORM)"
TARBALL="node-${NODE_VERSION}-${PLATFORM}.tar.gz"
URL="${NODE_MIRROR}/${NODE_VERSION}/${TARBALL}"
mkdir -p "$NODE_INSTALL_DIR"
if command -v curl >/dev/null 2>&1; then
curl -# -L "$URL" -o "/tmp/$TARBALL"
elif command -v wget >/dev/null 2>&1; then
wget -q --show-progress "$URL" -O "/tmp/$TARBALL"
else
echo -e " ${RED}✗ Neither curl nor wget is available.${NC}\n Install one of them, then run this again."
exit 1
fi
tar -xzf "/tmp/$TARBALL" -C "$NODE_INSTALL_DIR" --strip-components=1
rm -f "/tmp/$TARBALL"
chmod +x "$NODE_INSTALL_DIR/bin/node"
if [ -f "$NODE_INSTALL_DIR/bin/node" ]; then
echo -e " ${GREEN}${NC} Node.js installed"
INSTALL_NODE="$NODE_INSTALL_DIR/bin/node"
INSTALL_NPM="$NODE_INSTALL_DIR/lib/node_modules/npm/bin/npm-cli.js"
else
echo -e " ${RED}✗ The Node.js download did not finish.${NC}\n Usually a dropped connection. Check your network and run this again."
exit 1
fi
fi
fi
# Helper: invoke npm. For bundled Node we have npm-cli.js (run via node).
# For system Node, INSTALL_NPM is the npm shell wrapper (executable).
run_npm() {
if [ "$USE_SYSTEM_NODE" = "true" ]; then
"$INSTALL_NPM" "$@"
else
"$INSTALL_NODE" "$INSTALL_NPM" "$@"
fi
}
run_node() {
if [ "$USE_SYSTEM_NODE" = "true" ]; then
node "$@"
else
"$INSTALL_NODE" "$@"
fi
}
echo ""
# ============================================================
# Step 3: OpenClaw 安装
# ============================================================
echo -e " ${BOLD}[2/7] Installing OpenClaw${NC}"
if [ -d "$CORE_DIR/node_modules/openclaw" ]; then
echo -e " ${GREEN}${NC} OpenClaw already installed"
else
if [ ! -f "$CORE_DIR/package.json" ]; then
cat > "$CORE_DIR/package.json" << PKGJSON
{
"name": "u-claw-core",
"version": "1.0.0",
"private": true,
"dependencies": {
"openclaw": "$OPENCLAW_VERSION"
}
}
PKGJSON
fi
echo -e " ${CYAN}${NC} Downloading from the npm registry"
NPM_LOG=$(mktemp /tmp/uclaw-npm.XXXXXX.log)
if run_npm install --prefix "$CORE_DIR" --registry="$MIRROR" >"$NPM_LOG" 2>&1; then
tail -5 "$NPM_LOG"
rm -f "$NPM_LOG"
echo -e " ${GREEN}${NC} OpenClaw installed"
else
echo -e " ${RED}✗ OpenClaw could not be installed. Full log: $NPM_LOG${NC}"
tail -20 "$NPM_LOG"
exit 1
fi
fi
echo ""
# ============================================================
# Step 4: QQ 插件(非致命)
# ============================================================
echo -e " ${BOLD}[3/7] Installing the QQ plugin${NC}"
if [ -d "$CORE_DIR/node_modules/@sliverp/qqbot" ]; then
echo -e " ${GREEN}${NC} QQ plugin already installed"
else
echo -e " ${CYAN}${NC} Installing the QQ plugin"
run_npm install @sliverp/qqbot@latest --prefix "$CORE_DIR" --registry="$MIRROR" 2>/dev/null || {
echo -e " ${YELLOW}${NC} QQ plugin failed to install. Everything else still works."
}
if [ -d "$CORE_DIR/node_modules/@sliverp/qqbot" ]; then
echo -e " ${GREEN}${NC} QQ plugin installed"
fi
fi
echo ""
# ============================================================
# Step 5: Install skills from the manifest
# ============================================================
# Skill content lives in skills/ and is described by skills/manifest.json.
# This script carries none of it, which is what keeps the shell and PowerShell
# installers identical (tests/skills-manifest.test.mjs enforces that).
echo -e " ${BOLD}[4/7] Installing skills ...${NC}"
SKILLS_TARGET="$CORE_DIR/node_modules/openclaw/skills"
mkdir -p "$SKILLS_TARGET"
SKILL_INSTALLER="$SCRIPT_DIR/../lib/install-skills.mjs"
if [ -f "$SKILL_INSTALLER" ]; then
run_node "$SKILL_INSTALLER" --target "$SKILLS_TARGET" --locale "$UCLAW_LOCALE"
else
# curl | bash has no checkout: fetch the installer, then let it fetch content.
SKILL_TMP=$(mktemp "${TMPDIR:-/tmp}/uclaw-skills.XXXXXX.mjs")
if curl -fsSL "$SKILL_INSTALLER_URL" -o "$SKILL_TMP"; then
run_node "$SKILL_TMP" --target "$SKILLS_TARGET" --locale "$UCLAW_LOCALE" --ref "$UCLAW_REF"
rm -f "$SKILL_TMP"
else
rm -f "$SKILL_TMP"
echo -e " ${RED}${NC} Could not download the skill installer — skipping skills."
echo -e " You can add them later from the Skill Hub."
fi
fi
echo ""
# ============================================================
# Step 6: 交互式模型配置
# ============================================================
echo -e " ${BOLD}[5/7] Choosing a model${NC}"
echo ""
# 如果已有配置且包含 apiKey跳过
if [ -f "$CONFIG_PATH" ] && grep -q '"apiKey"' "$CONFIG_PATH" 2>/dev/null; then
echo -e " ${GREEN}${NC} A model is already configured"
else
# 检测是否可交互
if [ -t 0 ]; then
echo -e " Which model do you want to use?"
echo ""
echo -e " ${GREEN}1)${NC} Gemini easiest to get a key for, free tier"
echo -e " ${NC}2)${NC} Claude best all-rounder"
echo -e " ${NC}3)${NC} GPT most widely used"
echo -e " ${NC}4)${NC} OpenRouter one key, many models"
echo -e " ${NC}5)${NC} SEA-LION Malay, Tamil and Singlish"
echo -e " ${NC}6)${NC} Local runs offline via Ollama"
echo ""
echo -e " ${DIM}7) Other providers (DeepSeek, Kimi, Qwen, GLM, ...)${NC}"
echo ""
read -p " Pick a number [1]: " MODEL_CHOICE
MODEL_CHOICE=${MODEL_CHOICE:-1}
echo ""
PROVIDER="custom"
NEED_KEY=true
case $MODEL_CHOICE in
1)
MODEL_NAME="gemini-2.5-flash"
BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai"
KEY_LABEL="Gemini API key"
KEY_HINT="Get one at https://aistudio.google.com/apikey — sign in with Google, no card needed"
;;
2)
MODEL_NAME="claude-sonnet-4-20250514"
BASE_URL="https://api.anthropic.com/v1"
KEY_LABEL="Anthropic API key"
KEY_HINT="Get one at https://console.anthropic.com/settings/keys"
;;
3)
MODEL_NAME="gpt-4o"
BASE_URL="https://api.openai.com/v1"
KEY_LABEL="OpenAI API key"
KEY_HINT="Get one at https://platform.openai.com/api-keys"
;;
4)
MODEL_NAME="anthropic/claude-sonnet-4"
BASE_URL="https://openrouter.ai/api/v1"
KEY_LABEL="OpenRouter API key"
KEY_HINT="Get one at https://openrouter.ai/keys"
;;
5)
MODEL_NAME="aisingapore/Qwen-SEA-LION-v4.5-27B-IT"
BASE_URL="https://api.sea-lion.ai/v1"
KEY_LABEL="SEA-LION API key"
KEY_HINT="Get one at https://playground.sea-lion.ai — free, but rate-limited to 10 requests a minute"
;;
6)
MODEL_NAME="llama3.2"
BASE_URL="http://127.0.0.1:11434/v1"
KEY_LABEL=""
KEY_HINT="Install Ollama from https://ollama.com, then run: ollama run llama3.2"
NEED_KEY=false
;;
7)
echo -e " Paste the endpoint and model for your provider."
read -p " API endpoint (e.g. https://api.example.com/v1): " BASE_URL
read -p " Model name: " MODEL_NAME
KEY_LABEL="API key"
KEY_HINT="Use the key your provider issued you"
;;
*)
echo -e " ${YELLOW}Not a listed option — using Gemini.${NC}"
MODEL_NAME="gemini-2.5-flash"
BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai"
KEY_LABEL="Gemini API key"
KEY_HINT="Get one at https://aistudio.google.com/apikey"
;;
esac
echo ""
# 输入 API Key
API_KEY=""
if [ "$NEED_KEY" = "true" ]; then
echo -e " ${CYAN}$KEY_HINT${NC}"
echo ""
read -p " Paste your $KEY_LABEL: " API_KEY
if [ -z "$API_KEY" ]; then
echo -e " ${YELLOW}⚠ No key entered. You can add one later from Settings.${NC}"
fi
else
echo -e " ${CYAN}$KEY_HINT${NC}"
fi
# 转义 API_KEY 中可能破坏 JSON 的字符 (反斜杠和双引号)。这里只处理这两个,
# 因为合法的 API key 极少包含控制字符。如果用户粘贴了奇怪的内容,至少 JSON 仍然可解析。
API_KEY_JSON=${API_KEY//\\/\\\\}
API_KEY_JSON=${API_KEY_JSON//\"/\\\"}
# 写配置文件
if [ "$PROVIDER" = "custom" ] && [ -n "$BASE_URL" ]; then
cat > "$CONFIG_PATH" << CFGEOF
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
"models": {
"mode": "merge",
"providers": {
"custom": {
"baseUrl": "$BASE_URL",
"apiKey": "$API_KEY_JSON",
"api": "openai-completions",
"models": [{ "id": "$MODEL_NAME" }]
}
}
},
"agents": { "defaults": { "model": { "primary": "custom/$MODEL_NAME" } } }
}
CFGEOF
elif [ "$PROVIDER" = "anthropic" ]; then
cat > "$CONFIG_PATH" << CFGEOF
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
"models": {
"mode": "merge",
"providers": {
"anthropic": {
"apiKey": "$API_KEY_JSON",
"api": "anthropic",
"models": [{ "id": "$MODEL_NAME" }]
}
}
},
"agents": { "defaults": { "model": { "primary": "anthropic/$MODEL_NAME" } } }
}
CFGEOF
elif [ "$PROVIDER" = "openai" ]; then
cat > "$CONFIG_PATH" << CFGEOF
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
"models": {
"mode": "merge",
"providers": {
"openai": {
"apiKey": "$API_KEY_JSON",
"api": "openai-completions",
"models": [{ "id": "$MODEL_NAME" }]
}
}
},
"agents": { "defaults": { "model": { "primary": "openai/$MODEL_NAME" } } }
}
CFGEOF
fi
echo -e " ${GREEN}${NC} Model set: $MODEL_NAME"
else
# 非交互模式,写默认配置
echo -e " ${YELLOW}${NC} Running non-interactively — skipping model setup."
echo -e " ${DIM}Pick a model from Settings after the first launch.${NC}"
if [ ! -f "$CONFIG_PATH" ]; then
cat > "$CONFIG_PATH" << 'CFGEOF'
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
}
}
CFGEOF
fi
fi
fi
echo ""
# ============================================================
# Step 7: 生成启动脚本 + 验证 + 摘要
# ============================================================
echo -e " ${BOLD}[6/7] Writing the start script${NC}"
# Mac 启动脚本
cat > "$UCLAW_DIR/start.command" << 'STARTEOF'
#!/bin/bash
DIR="$(cd "$(dirname "$0")" && pwd)"
ARCH=$(uname -m)
# Find Node.js
if [ "$ARCH" = "arm64" ]; then
NODE_BIN="$DIR/runtime/node-mac-arm64/bin/node"
else
NODE_BIN="$DIR/runtime/node-mac-x64/bin/node"
fi
[ ! -f "$NODE_BIN" ] && NODE_BIN="$(which node 2>/dev/null)"
if [ -z "$NODE_BIN" ] || [ ! -f "$NODE_BIN" ]; then
echo "U-Claw cannot start: the bundled Node.js is missing."; echo "Reinstall with: curl -fsSL https://u-claw.org/install.sh | bash"
exit 1
fi
OPENCLAW_MJS="$DIR/core/node_modules/openclaw/openclaw.mjs"
if [ ! -f "$OPENCLAW_MJS" ]; then
echo "U-Claw cannot start: OpenClaw is missing."; echo "Reinstall with: curl -fsSL https://u-claw.org/install.sh | bash"
exit 1
fi
export OPENCLAW_HOME="$DIR/data"
export OPENCLAW_STATE_DIR="$DIR/data/.openclaw"
export OPENCLAW_CONFIG_PATH="$DIR/data/.openclaw/openclaw.json"
# Find available port
PORT=18789
while lsof -i :$PORT >/dev/null 2>&1; do
PORT=$((PORT + 1))
[ $PORT -gt 18799 ] && echo "No free port between 18789 and 18799 — U-Claw may already be running." && exit 1
done
cd "$DIR/core"
"$NODE_BIN" "$OPENCLAW_MJS" gateway run --allow-unconfigured --force --port $PORT &
PID=$!
# Wait for gateway to start, then open browser
for i in $(seq 1 30); do
sleep 0.5
if curl -s -o /dev/null "http://127.0.0.1:$PORT/" 2>/dev/null; then
open "http://127.0.0.1:$PORT/#token=uclaw" 2>/dev/null || true
break
fi
done
echo ""
echo " U-Claw is running: http://127.0.0.1:$PORT/#token=uclaw"
echo " Press Ctrl+C to stop."
echo ""
wait $PID
STARTEOF
chmod +x "$UCLAW_DIR/start.command"
# Linux 启动脚本
if [ "$OS" = "Linux" ]; then
cat > "$UCLAW_DIR/start.sh" << 'STARTEOF'
#!/bin/bash
DIR="$(cd "$(dirname "$0")" && pwd)"
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then
NODE_BIN="$DIR/runtime/node-linux-x64/bin/node"
elif [ "$ARCH" = "aarch64" ]; then
NODE_BIN="$DIR/runtime/node-linux-arm64/bin/node"
fi
[ ! -f "$NODE_BIN" ] && NODE_BIN="$(which node 2>/dev/null)"
OPENCLAW_MJS="$DIR/core/node_modules/openclaw/openclaw.mjs"
export OPENCLAW_HOME="$DIR/data"
export OPENCLAW_STATE_DIR="$DIR/data/.openclaw"
export OPENCLAW_CONFIG_PATH="$DIR/data/.openclaw/openclaw.json"
PORT=18789
while ss -tln | grep -q ":$PORT "; do
PORT=$((PORT + 1))
[ $PORT -gt 18799 ] && echo "No free port — U-Claw may already be running." && exit 1
done
cd "$DIR/core"
"$NODE_BIN" "$OPENCLAW_MJS" gateway run --allow-unconfigured --force --port $PORT &
PID=$!
sleep 2
echo ""
echo " U-Claw is running: http://127.0.0.1:$PORT/#token=uclaw"
echo " Press Ctrl+C to stop."
echo ""
xdg-open "http://127.0.0.1:$PORT/#token=uclaw" 2>/dev/null || true
wait $PID
STARTEOF
chmod +x "$UCLAW_DIR/start.sh"
fi
echo -e " ${GREEN}${NC} Start script written"
echo ""
# ============================================================
# 验证
# ============================================================
echo -e " ${BOLD}[7/7] Checking the install${NC}"
echo ""
# Node.js
if [ -n "$INSTALL_NODE" ] && "$INSTALL_NODE" --version >/dev/null 2>&1; then
NODE_VER_STR=$("$INSTALL_NODE" --version)
echo -e " ${GREEN}[✓]${NC} Node.js $NODE_VER_STR"
else
echo -e " ${RED}[✗]${NC} Node.js"
fi
# OpenClaw
if [ -f "$CORE_DIR/node_modules/openclaw/openclaw.mjs" ]; then
echo -e " ${GREEN}[✓]${NC} OpenClaw"
else
echo -e " ${RED}[✗]${NC} OpenClaw"
fi
# QQ 插件
if [ -d "$CORE_DIR/node_modules/@sliverp/qqbot" ]; then
echo -e " ${GREEN}[✓]${NC} QQ plugin"
else
echo -e " ${YELLOW}[⚠]${NC} QQ plugin not installed — everything else works"
fi
# 技能
INSTALLED_SKILLS=$(ls -d "$SKILLS_TARGET"/*/ 2>/dev/null | wc -l | tr -d ' ')
echo -e " ${GREEN}[✓]${NC} Skills (${INSTALLED_SKILLS})"
# 配置文件
if [ -f "$CONFIG_PATH" ]; then
echo -e " ${GREEN}[✓]${NC} Config file"
else
echo -e " ${YELLOW}[⚠]${NC} No config yet — set it up on first launch"
fi
echo ""
# ============================================================
# 摘要
# ============================================================
INSTALL_SIZE=$(du -sh "$UCLAW_DIR" 2>/dev/null | cut -f1)
echo -e "${GREEN}${BOLD}"
echo " ╔══════════════════════════════════════════╗"
echo " ║ U-Claw is installed ║"
echo " ╚══════════════════════════════════════════╝"
echo -e "${NC}"
echo -e " ${BOLD}Location:${NC} $UCLAW_DIR"
echo -e " ${BOLD}Size:${NC} $INSTALL_SIZE"
echo ""
echo -e " ${BOLD}To start it:${NC}"
if [ "$OS" = "Darwin" ]; then
echo -e " Double-click ${CYAN}~/.uclaw/start.command${NC}"
echo -e " or run ${CYAN}bash ~/.uclaw/start.command${NC}"
else
echo -e " Run ${CYAN}bash ~/.uclaw/start.sh${NC}"
fi
echo ""
echo -e " ${BOLD}Then:${NC}"
echo -e " Your browser opens and you can start talking."
echo ""
echo -e " ${DIM}To change model or key later, open Settings from the app.${NC}"
echo -e " ${DIM}To remove it: rm -rf ~/.uclaw${NC}"
echo ""