feat(usb-release): 统一发布版,Mac + Windows 跨平台支持
基于 Pro 精简结构,去掉 app/ 中间层,core/runtime/data/ 直接在根目录。 - Mac: 启动 U-Claw.command + 重新配置.command(含 Config GUI 首次配置) - Windows: Windows-Start.bat + Windows-Menu.bat(15 项功能菜单) - setup.sh: --all-platforms 三平台 runtime, --with-toolkit 下载安装包 - 10 个中国优化技能 (skills/) - toolkit/ 放 Node.js pkg/msi 供离线装机
This commit is contained in:
183
usb-release/启动 U-Claw.command
Executable file
183
usb-release/启动 U-Claw.command
Executable file
@@ -0,0 +1,183 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# U-Claw — 便携版启动器 (macOS)
|
||||
# 双击此文件即可启动 OpenClaw
|
||||
# ============================================================
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ARCH=$(uname -m)
|
||||
|
||||
# --- Colors ---
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN} 🦞 U-Claw — 便携版${NC}"
|
||||
echo -e " ════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# --- 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 -e "${RED} ✗ 未找到 Node.js,请确保 runtime/ 目录完整${NC}"
|
||||
echo " 按回车退出..."; read; exit 1
|
||||
fi
|
||||
NODE_VER=$("$NODE_BIN" --version 2>/dev/null)
|
||||
echo -e " ${GREEN}✓${NC} Node.js: $NODE_VER ($ARCH)"
|
||||
|
||||
# --- Remove macOS quarantine ---
|
||||
if xattr -l "$NODE_BIN" 2>/dev/null | grep -q "com.apple.quarantine"; then
|
||||
echo -e " ${YELLOW}移除 macOS 安全限制...${NC}"
|
||||
xattr -rd com.apple.quarantine "$DIR" 2>/dev/null || true
|
||||
echo -e " ${GREEN}✓${NC} 已移除"
|
||||
fi
|
||||
|
||||
# --- Find OpenClaw ---
|
||||
CORE_DIR="$DIR/core"
|
||||
OPENCLAW_MJS="$CORE_DIR/node_modules/openclaw/openclaw.mjs"
|
||||
if [ ! -f "$OPENCLAW_MJS" ]; then
|
||||
echo -e "${RED} ✗ 未找到 OpenClaw,请确保 core/ 目录完整${NC}"
|
||||
echo " 按回车退出..."; read; exit 1
|
||||
fi
|
||||
echo -e " ${GREEN}✓${NC} OpenClaw: 已就绪"
|
||||
|
||||
# --- Setup portable data dir ---
|
||||
DATA_DIR="$DIR/data"
|
||||
export OPENCLAW_HOME="$DATA_DIR"
|
||||
export OPENCLAW_STATE_DIR="$DATA_DIR/.openclaw"
|
||||
export OPENCLAW_CONFIG_PATH="$DATA_DIR/.openclaw/openclaw.json"
|
||||
mkdir -p "$DATA_DIR/.openclaw" "$DATA_DIR/memory" "$DATA_DIR/backups" "$DATA_DIR/logs"
|
||||
|
||||
# --- Check if config needs setup (first run) ---
|
||||
CONFIG_FILE="$DATA_DIR/.openclaw/openclaw.json"
|
||||
NEEDS_CONFIG=false
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
NEEDS_CONFIG=true
|
||||
else
|
||||
if ! grep -qE '"apiKey"|"ZAI_API_KEY"|"providers"' "$CONFIG_FILE" 2>/dev/null; then
|
||||
NEEDS_CONFIG=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- If first run, open Config GUI first ---
|
||||
if [ "$NEEDS_CONFIG" = "true" ]; then
|
||||
echo ""
|
||||
echo -e " ${YELLOW}⚡ 首次运行,请先配置模型和 API Key${NC}"
|
||||
echo -e " ${YELLOW} 正在打开配置页面...${NC}"
|
||||
echo ""
|
||||
|
||||
CONFIG_PORT=18780
|
||||
while lsof -i :$CONFIG_PORT >/dev/null 2>&1; do
|
||||
CONFIG_PORT=$((CONFIG_PORT + 1))
|
||||
[ $CONFIG_PORT -gt 18785 ] && break
|
||||
done
|
||||
|
||||
"$NODE_BIN" -e "
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const configPath = '$CONFIG_FILE';
|
||||
const htmlPath = path.join('$DIR', 'Config.html');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
||||
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
||||
res.end(fs.readFileSync(htmlPath, 'utf-8'));
|
||||
} else if (req.method === 'GET' && req.url === '/api/config') {
|
||||
try {
|
||||
const cfg = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, 'utf-8')) : {};
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.end(JSON.stringify(cfg));
|
||||
} catch(e) {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.end('{}');
|
||||
}
|
||||
} else if (req.method === 'POST' && req.url === '/api/config') {
|
||||
let body = '';
|
||||
req.on('data', c => body += c);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const cfg = JSON.parse(body);
|
||||
fs.mkdirSync(path.dirname(configPath), {recursive: true});
|
||||
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.end('{\"ok\":true}');
|
||||
} catch(e) {
|
||||
res.writeHead(400, {'Content-Type': 'application/json'});
|
||||
res.end('{\"error\":\"' + e.message + '\"}');
|
||||
}
|
||||
});
|
||||
} else if (req.method === 'POST' && req.url === '/api/done') {
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.end('{\"ok\":true}');
|
||||
setTimeout(() => { server.close(); process.exit(0); }, 300);
|
||||
} else {
|
||||
res.writeHead(404); res.end('Not Found');
|
||||
}
|
||||
});
|
||||
server.listen($CONFIG_PORT, '127.0.0.1', () => {
|
||||
console.log('Config server: http://127.0.0.1:$CONFIG_PORT');
|
||||
});
|
||||
" &
|
||||
CONFIG_PID=$!
|
||||
sleep 1
|
||||
open "http://127.0.0.1:$CONFIG_PORT/"
|
||||
|
||||
echo -e " ${CYAN}等待配置完成...${NC}"
|
||||
echo -e " (在浏览器中选择模型、填写 API Key,点击「保存并启动」)"
|
||||
echo ""
|
||||
wait $CONFIG_PID 2>/dev/null
|
||||
echo -e " ${GREEN}✓${NC} 配置已保存"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# --- Check dependencies ---
|
||||
if [ ! -d "$CORE_DIR/node_modules" ]; then
|
||||
echo -e " ${YELLOW}首次运行,安装依赖...${NC}"
|
||||
NODE_DIR="$(dirname "$(dirname "$NODE_BIN")")"
|
||||
cd "$CORE_DIR"
|
||||
"$NODE_BIN" "$NODE_DIR/bin/npm" install --registry=https://registry.npmmirror.com 2>&1
|
||||
echo -e " ${GREEN}✓${NC} 依赖安装完成"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# --- Find available port ---
|
||||
PORT=18789
|
||||
while lsof -i :$PORT >/dev/null 2>&1; do
|
||||
PORT=$((PORT + 1))
|
||||
[ $PORT -gt 18799 ] && echo -e "${RED} ✗ 没有可用端口 (18789-18799)${NC}" && exit 1
|
||||
done
|
||||
|
||||
echo -e " ${CYAN}🚀 正在启动 OpenClaw...${NC}"
|
||||
echo ""
|
||||
|
||||
# --- Start OpenClaw gateway ---
|
||||
cd "$CORE_DIR"
|
||||
"$NODE_BIN" "$OPENCLAW_MJS" gateway run --allow-unconfigured --force --port $PORT &
|
||||
GW_PID=$!
|
||||
|
||||
# --- Wait for gateway, 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
|
||||
TOKEN=$("$NODE_BIN" -e "try{const c=JSON.parse(require('fs').readFileSync('$CONFIG_FILE','utf8'));console.log(c.gateway?.auth?.token||'uclaw')}catch(e){console.log('uclaw')}" 2>/dev/null)
|
||||
open "http://127.0.0.1:$PORT/#token=$TOKEN" 2>/dev/null || true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
TOKEN=$("$NODE_BIN" -e "try{const c=JSON.parse(require('fs').readFileSync('$CONFIG_FILE','utf8'));console.log(c.gateway?.auth?.token||'uclaw')}catch(e){console.log('uclaw')}" 2>/dev/null)
|
||||
echo -e " ${GREEN}════════════════════════════════${NC}"
|
||||
echo -e " ${GREEN}🦞 U-Claw 正在运行!${NC}"
|
||||
echo -e " ${GREEN} 地址: http://127.0.0.1:$PORT/#token=$TOKEN${NC}"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}按 Ctrl+C 停止服务${NC}"
|
||||
echo -e " ${GREEN}════════════════════════════════${NC}"
|
||||
echo ""
|
||||
|
||||
trap "kill $GW_PID 2>/dev/null; echo ''; echo ' 🦞 U-Claw 已停止'; exit 0" INT TERM
|
||||
wait $GW_PID
|
||||
Reference in New Issue
Block a user