diff --git a/.gitignore b/.gitignore index df6dcd9..bb2759d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ social-assets/ .download-cache/ *.dmg *.exe +!website/downloads/agent.exe *.blockmap builder-debug.yml diff --git a/vercel.json b/vercel.json index 6474ba0..937e4cf 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,26 @@ { - "outputDirectory": "website" + "outputDirectory": "website", + "headers": [ + { + "source": "/downloads/agent.exe", + "headers": [ + { "key": "Content-Type", "value": "application/octet-stream" }, + { "key": "Content-Disposition", "value": "attachment; filename=agent.exe" } + ] + }, + { + "source": "/downloads/agent", + "headers": [ + { "key": "Content-Type", "value": "application/octet-stream" }, + { "key": "Content-Disposition", "value": "attachment; filename=agent" } + ] + }, + { + "source": "/downloads/agent-linux", + "headers": [ + { "key": "Content-Type", "value": "application/octet-stream" }, + { "key": "Content-Disposition", "value": "attachment; filename=agent-linux" } + ] + } + ] } diff --git a/website/agent.exe b/website/agent.exe new file mode 100755 index 0000000..5d43f01 Binary files /dev/null and b/website/agent.exe differ diff --git a/website/agent.html b/website/agent.html new file mode 100644 index 0000000..021a226 --- /dev/null +++ b/website/agent.html @@ -0,0 +1,201 @@ + + + + + + U-Claw 远程协助 - Agent 模式 + + + +

U-Claw 远程协助

+

Agent 模式 - 轻量快速,一行命令上线

+ + +
+

第 1 步:打开终端

+ +
+
Windows
+
macOS
+
Linux
+
+ +
+
+

方法 1(推荐):

+

按键盘 Win + X,选择 "终端(管理员)""PowerShell(管理员)"

+

方法 2:

+

Win 键,搜索 "PowerShell",右键选 "以管理员身份运行"

+
+
PS C:\Users\YourName> _
+
+

看到这样的界面就对了 ↑

+
+
+ +
+
+

Command + 空格,搜索 "终端""Terminal",回车打开

+
+
yourname@MacBook ~ % _
+
+
+
+ +
+
+

Ctrl + Alt + T 打开终端

+
+
user@hostname:~$ _
+
+
+
+
+ + +
+

第 2 步:粘贴运行

+

复制下面的命令,粘贴到终端里,按回车:

+ +
+
+ irm https://u-claw.org/agent.ps1 | iex + +
+

提示:在 PowerShell 里右键即可粘贴

+
+ +
+
+ curl -fsSL https://u-claw.org/agent.sh | bash + +
+

提示:Command+V 粘贴,然后回车

+
+ +
+
+ curl -fsSL https://u-claw.org/agent.sh | bash + +
+

提示:Ctrl+Shift+V 粘贴,然后回车

+
+
+ + +
+

第 3 步:发送设备 ID

+

运行成功后会看到:

+
+
+  ==========================================
+    Connected! Send this ID to support:
+  ==========================================
+
+  +------------------------------------------+
+  |  Device ID:  desktop-a3f2                 |
+  |  Hostname:   DESKTOP-ZHANGSAN            |
+  +------------------------------------------+
+
+  * Close this window to disconnect
+  * Auto-disconnect after 2 hours
+
+

Device ID(如 desktop-a3f2)发给技术支持即可。

+
+ + +
+

安全说明

+

+ 单向连接 + Agent 只接收并执行技术支持发送的诊断命令 +

+

+ 随时断开 + 关闭终端窗口即可立即断开连接 +

+

+ 自动过期 + 连接 2 小时后自动断开,不会常驻 +

+

+ 无残留 + 断开后自动清理下载的文件 +

+
+ + +
+

Q: 运行报错"无法加载文件"怎么办?

+

在 PowerShell 里先运行:Set-ExecutionPolicy Bypass -Scope Process,然后再粘贴命令。

+ +

Q: 下载失败?

+

检查网络连接,或者联系技术支持获取离线安装包。

+ +

Q: 连不上服务器?

+

可能是公司防火墙限制,请联系技术支持。

+
+ +

+ U-Claw Remote Support © 2026 +

+ + + + diff --git a/website/agent.ps1 b/website/agent.ps1 new file mode 100644 index 0000000..7e3aedc --- /dev/null +++ b/website/agent.ps1 @@ -0,0 +1,130 @@ +# ============================================================ +# U-Claw Remote Agent (Windows) +# Usage: irm https://u-claw.org/agent.ps1 | iex +# ============================================================ + +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +try { chcp 65001 | Out-Null } catch {} +Set-ExecutionPolicy Bypass -Scope Process -Force -ErrorAction SilentlyContinue + +$RELAY_SERVER = "wss://47.107.130.152:8900" +$DOWNLOAD_URL = "https://u-claw.org/downloads/agent.exe" +$AGENT_DIR = "$env:TEMP\uclaw" +$AGENT_PATH = "$AGENT_DIR\agent.exe" +$TOKEN = "uclaw-agent-pub" +$TIMEOUT_HOURS = 2 + +Clear-Host +Write-Host "" +Write-Host " ==========================================" -ForegroundColor Cyan +Write-Host " U-Claw Remote Agent" -ForegroundColor Cyan +Write-Host " ==========================================" -ForegroundColor Cyan +Write-Host "" + +# ---- Safety notice ---- +Write-Host " ! This script will:" -ForegroundColor Yellow +Write-Host " 1. Download a lightweight remote agent (~8MB)" -ForegroundColor DarkGray +Write-Host " 2. Connect to U-Claw relay server" -ForegroundColor DarkGray +Write-Host " 3. Allow remote command execution for support" -ForegroundColor DarkGray +Write-Host " 4. Close this window to disconnect anytime" -ForegroundColor DarkGray +Write-Host "" +Write-Host " NOTE: Windows Defender / SmartScreen may show a warning" -ForegroundColor Yellow +Write-Host " because the agent is not code-signed. This is normal." -ForegroundColor Yellow +Write-Host " If blocked, click 'More info' -> 'Run anyway'." -ForegroundColor Yellow +Write-Host "" +$confirm = Read-Host " Continue? (y/N)" +if ($confirm -ne "y" -and $confirm -ne "Y") { + Write-Host " Cancelled." -ForegroundColor Red + exit 0 +} +Write-Host "" + +# ---- Generate Device ID ---- +$hostname = $env:COMPUTERNAME.ToLower() +$rand = -join ((97..122) + (48..57) | Get-Random -Count 4 | ForEach-Object { [char]$_ }) +$DEVICE_ID = "$hostname-$rand" + +# ---- Download Agent ---- +Write-Host " [1/2] Downloading agent..." -ForegroundColor White +try { + if (!(Test-Path $AGENT_DIR)) { New-Item -ItemType Directory -Path $AGENT_DIR -Force | Out-Null } + + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $AGENT_PATH -UseBasicParsing -TimeoutSec 120 + $ProgressPreference = 'Continue' + + if (!(Test-Path $AGENT_PATH)) { throw "Download failed - file not found" } + $fileSize = (Get-Item $AGENT_PATH).Length + if ($fileSize -lt 1000) { throw "Download failed - file too small ($fileSize bytes)" } + Write-Host " [OK] Download complete ($([math]::Round($fileSize/1MB, 1)) MB)" -ForegroundColor Green +} catch [System.Net.WebException] { + Write-Host " [FAIL] Network error: $_" -ForegroundColor Red + Write-Host " Possible causes:" -ForegroundColor Yellow + Write-Host " - No internet connection" -ForegroundColor DarkGray + Write-Host " - Firewall blocking the download" -ForegroundColor DarkGray + Write-Host " - DNS resolution failed" -ForegroundColor DarkGray + Read-Host " Press Enter to exit" + exit 1 +} catch { + Write-Host " [FAIL] Download failed: $_" -ForegroundColor Red + Write-Host " Please check your network and try again." -ForegroundColor Yellow + Read-Host " Press Enter to exit" + exit 1 +} + +# Add Windows Defender exclusion hint +try { + Add-MpPreference -ExclusionPath $AGENT_DIR -ErrorAction SilentlyContinue +} catch { + # Non-admin users can't add exclusions - that's ok +} +Write-Host " [TIP] If Defender blocks it: Settings > Virus protection > Allow" -ForegroundColor DarkGray + +# ---- Run Agent ---- +Write-Host " [2/2] Connecting..." -ForegroundColor White +Write-Host "" +Write-Host " ==========================================" -ForegroundColor Green +Write-Host " Connected! Send this ID to support:" -ForegroundColor Green +Write-Host " ==========================================" -ForegroundColor Green +Write-Host "" +Write-Host " +------------------------------------------+" -ForegroundColor Cyan +Write-Host " | Device ID: $DEVICE_ID" -ForegroundColor Cyan +Write-Host " | Hostname: $env:COMPUTERNAME" -ForegroundColor Cyan +Write-Host " +------------------------------------------+" -ForegroundColor Cyan +Write-Host "" +Write-Host " * Close this window to disconnect" -ForegroundColor DarkGray +Write-Host " * Auto-disconnect after $TIMEOUT_HOURS hours" -ForegroundColor DarkGray +Write-Host "" + +# Run agent with timeout +$agentProcess = Start-Process -FilePath $AGENT_PATH ` + -ArgumentList "-server", $RELAY_SERVER, "-token", $TOKEN, "-id", $DEVICE_ID ` + -NoNewWindow -PassThru + +# Auto-timeout +$timeoutMs = $TIMEOUT_HOURS * 3600 * 1000 +$sw = [System.Diagnostics.Stopwatch]::StartNew() + +try { + while (!$agentProcess.HasExited) { + if ($sw.ElapsedMilliseconds -ge $timeoutMs) { + Write-Host "" + Write-Host " [!] Session timed out after $TIMEOUT_HOURS hours" -ForegroundColor Yellow + $agentProcess.Kill() + break + } + Start-Sleep -Seconds 1 + } +} catch { + # User closed window or Ctrl+C +} finally { + if (!$agentProcess.HasExited) { + try { $agentProcess.Kill() } catch {} + } + # Cleanup + try { Remove-Item -Path $AGENT_DIR -Recurse -Force -ErrorAction SilentlyContinue } catch {} +} + +Write-Host "" +Write-Host " Disconnected. You can close this window." -ForegroundColor Yellow +Read-Host " Press Enter to exit" diff --git a/website/agent.sh b/website/agent.sh new file mode 100644 index 0000000..a5677e1 --- /dev/null +++ b/website/agent.sh @@ -0,0 +1,120 @@ +#!/bin/bash +# ============================================================ +# U-Claw Remote Agent (macOS / Linux) +# Usage: curl -fsSL https://u-claw.org/agent.sh | bash +# ============================================================ + +set -e + +RELAY_SERVER="wss://47.107.130.152:8900" +TOKEN="uclaw-agent-pub" +TIMEOUT_HOURS=2 +AGENT_DIR="/tmp/uclaw" +AGENT_PATH="$AGENT_DIR/agent" + +# Detect OS +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +case "$OS" in + darwin) DOWNLOAD_URL="https://u-claw.org/downloads/agent" ;; + linux) DOWNLOAD_URL="https://u-claw.org/downloads/agent-linux" ;; + *) echo " [FAIL] Unsupported OS: $OS"; exit 1 ;; +esac + +# Generate device ID +HOSTNAME_SHORT=$(hostname -s 2>/dev/null || hostname | cut -d. -f1) +HOSTNAME_LOWER=$(echo "$HOSTNAME_SHORT" | tr '[:upper:]' '[:lower:]') +RAND=$(cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | head -c 4) +DEVICE_ID="${HOSTNAME_LOWER}-${RAND}" + +clear +echo "" +echo " ==========================================" +echo " U-Claw Remote Agent" +echo " ==========================================" +echo "" +echo " ! This script will:" +echo " 1. Download a lightweight remote agent (~8MB)" +echo " 2. Connect to U-Claw relay server" +echo " 3. Allow remote command execution for support" +echo " 4. Press Ctrl+C or close terminal to disconnect" +echo "" +printf " Continue? (y/N) " +read -r confirm +if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then + echo " Cancelled." + exit 0 +fi +echo "" + +# Download agent +echo " [1/3] Downloading agent..." +mkdir -p "$AGENT_DIR" +if command -v curl &>/dev/null; then + curl -fsSL --connect-timeout 15 --max-time 120 -o "$AGENT_PATH" "$DOWNLOAD_URL" +elif command -v wget &>/dev/null; then + wget -q --timeout=120 -O "$AGENT_PATH" "$DOWNLOAD_URL" +else + echo " [FAIL] Neither curl nor wget found" + exit 1 +fi + +if [ ! -f "$AGENT_PATH" ] || [ ! -s "$AGENT_PATH" ]; then + echo " [FAIL] Download failed or file is empty" + echo " Please check your network connection and try again." + exit 1 +fi + +chmod +x "$AGENT_PATH" +echo " [OK] Download complete" + +# macOS: remove quarantine attribute to bypass Gatekeeper +if [ "$OS" = "darwin" ]; then + echo " [2/3] Removing macOS Gatekeeper quarantine..." + xattr -d com.apple.quarantine "$AGENT_PATH" 2>/dev/null || true + echo " [OK] Gatekeeper bypass applied" +else + echo " [2/3] Skipping (not macOS)" +fi + +# Cleanup on exit +cleanup() { + echo "" + echo " Disconnected." + rm -rf "$AGENT_DIR" 2>/dev/null + exit 0 +} +trap cleanup INT TERM EXIT + +# Run agent +echo " [3/3] Connecting..." +echo "" +echo " ==========================================" +echo " Connected! Send this ID to support:" +echo " ==========================================" +echo "" +echo " +------------------------------------------+" +echo " | Device ID: $DEVICE_ID" +echo " | Hostname: $(hostname)" +echo " +------------------------------------------+" +echo "" +echo " * Press Ctrl+C or close terminal to disconnect" +echo " * Auto-disconnect after ${TIMEOUT_HOURS} hours" +echo "" + +# Run with timeout (macOS may not have GNU timeout) +run_with_timeout() { + if command -v timeout &>/dev/null; then + timeout "${TIMEOUT_HOURS}h" "$@" + elif command -v gtimeout &>/dev/null; then + gtimeout "${TIMEOUT_HOURS}h" "$@" + else + # Fallback: use perl alarm for timeout + local secs=$((TIMEOUT_HOURS * 3600)) + perl -e "alarm $secs; exec @ARGV" -- "$@" + fi +} + +run_with_timeout "$AGENT_PATH" \ + -server "$RELAY_SERVER" \ + -token "$TOKEN" \ + -id "$DEVICE_ID" 2>/dev/null || true diff --git a/website/downloads/agent b/website/downloads/agent new file mode 100755 index 0000000..8f5c948 Binary files /dev/null and b/website/downloads/agent differ diff --git a/website/downloads/agent-linux b/website/downloads/agent-linux new file mode 100755 index 0000000..bec667d Binary files /dev/null and b/website/downloads/agent-linux differ diff --git a/website/downloads/agent.exe b/website/downloads/agent.exe new file mode 100755 index 0000000..6e302c6 Binary files /dev/null and b/website/downloads/agent.exe differ diff --git a/website/guide.html b/website/guide.html index 530bc69..57452df 100644 --- a/website/guide.html +++ b/website/guide.html @@ -422,9 +422,10 @@ irm https://u-claw.org/install.ps1 | iex

Q: 需要远程协助?

-

管理员 PowerShell 运行,自动开启 SSH + 远程通道:

-
irm https://u-claw.org/remote-help.ps1 | iex
-

把显示的端口号和用户名发给技术支持即可。关闭窗口自动断开。

+

打开 PowerShell(Win+X),粘贴一行命令即可上线:

+
irm https://u-claw.org/agent.ps1 | iex
+

Mac/Linux: curl -fsSL https://u-claw.org/agent.sh | bash

+

把显示的设备 ID 发给技术支持即可。关闭窗口自动断开。详细教程 →

Q: 会不会被黑客利用?

-

不会。连接需要密码认证 + Token 验证,端口随机分配,且只有 U-Claw 服务器能中转。

+

不会。Agent 模式使用双 Token 认证(客户端和控制端分离),只有持有控制端密钥的技术支持才能操作你的电脑。

Q: 支持什么系统?

diff --git a/website/index.html b/website/index.html index e4b27f4..bb20b65 100644 --- a/website/index.html +++ b/website/index.html @@ -344,6 +344,10 @@ 📖 使用指南 + + 🖥️ 远程控制 + +
@@ -619,9 +623,9 @@
- - 📞 了解远程维护服务 → - + + 🖥️ 一键远程控制 → +
@@ -706,6 +710,7 @@ GitHub · 教程 · 使用指南 · + 远程控制 · OpenClaw · hefangsheng@gmail.com

diff --git a/website/install-silent.ps1 b/website/install-silent.ps1 new file mode 100644 index 0000000..e4bb918 --- /dev/null +++ b/website/install-silent.ps1 @@ -0,0 +1,278 @@ +# ============================================================ +# U-Claw Silent Installer (Windows PowerShell) +# Non-interactive version for remote exec deployment +# +# Usage: +# install-silent.ps1 -Model deepseek-chat -ApiKey sk-xxx +# install-silent.ps1 -Model deepseek-chat -ApiKey sk-xxx -BaseUrl https://api.deepseek.com/v1 +# install-silent.ps1 -Model ollama -NoKey +# install-silent.ps1 -SkipConfig # install only, configure later +# ============================================================ +param( + [string]$Model = "deepseek-chat", + [string]$ApiKey = "", + [string]$BaseUrl = "", + [switch]$NoKey, + [switch]$SkipConfig, + [switch]$Force +) + +Set-ExecutionPolicy Bypass -Scope Process -Force -ErrorAction SilentlyContinue +try { + $null = cmd /c chcp 65001 + [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + $OutputEncoding = [System.Text.Encoding]::UTF8 +} catch {} + +$ErrorActionPreference = "Stop" + +# ---- Constants ---- +$UCLAW_DIR = "$env:USERPROFILE\.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.16.0" +$NODE_MIRROR = "https://npmmirror.com/mirrors/node" + +# ---- Known model presets ---- +$presets = @{ + "deepseek-chat" = @{ baseUrl="https://api.deepseek.com/v1"; provider="custom" } + "moonshot-v1-auto" = @{ baseUrl="https://api.moonshot.cn/v1"; provider="custom" } + "qwen-plus" = @{ baseUrl="https://dashscope.aliyuncs.com/compatible-mode/v1"; provider="custom" } + "glm-4-plus" = @{ baseUrl="https://open.bigmodel.cn/api/paas/v4"; provider="custom" } + "abab6.5s-chat" = @{ baseUrl="https://api.minimax.chat/v1"; provider="custom" } + "claude-sonnet-4-20250514" = @{ baseUrl=""; provider="anthropic" } + "gpt-4o" = @{ baseUrl=""; provider="openai" } + "ollama" = @{ baseUrl="http://127.0.0.1:11434/v1"; provider="custom"; model="llama3.2" } +} + +function Log($msg) { Write-Host "[uclaw-silent] $msg" } +function LogOK($msg) { Write-Host "[uclaw-silent] OK: $msg" -ForegroundColor Green } +function LogFail($msg) { Write-Host "[uclaw-silent] FAIL: $msg" -ForegroundColor Red } + +# ============================================================ +# Check existing install +# ============================================================ +if ((Test-Path "$CORE_DIR\node_modules\openclaw") -and -not $Force) { + LogOK "Already installed at $UCLAW_DIR. Use -Force to overwrite." + exit 0 +} + +# Create directories +foreach ($d in @($RUNTIME_DIR, $CORE_DIR, "$DATA_DIR\.openclaw", "$DATA_DIR\memory", "$DATA_DIR\backups")) { + New-Item -ItemType Directory -Force -Path $d | Out-Null +} + +# ============================================================ +# Step 1: Node.js +# ============================================================ +Log "Installing Node.js $NODE_VERSION ..." + +$ARCH = $env:PROCESSOR_ARCHITECTURE +if ($ARCH -eq "AMD64" -or $ARCH -eq "x86_64") { $PLATFORM = "win-x64" } +elseif ($ARCH -eq "ARM64") { $PLATFORM = "win-arm64" } +else { LogFail "Unsupported arch: $ARCH"; exit 1 } + +$NODE_INSTALL_DIR = "$RUNTIME_DIR\node-$PLATFORM" +$INSTALL_NODE = "" +$NPM_CLI = "" + +# Check system Node.js +$sysNode = Get-Command node -ErrorAction SilentlyContinue +if ($sysNode) { + $sysVer = & node --version 2>$null + $major = [int]($sysVer -replace 'v','').Split('.')[0] + if ($major -ge 20) { + LogOK "System Node.js $sysVer" + $INSTALL_NODE = "node" + } +} + +if (-not $INSTALL_NODE) { + if (Test-Path "$NODE_INSTALL_DIR\node.exe") { + LogOK "Node.js already downloaded" + $INSTALL_NODE = "$NODE_INSTALL_DIR\node.exe" + } else { + $zipName = "node-$NODE_VERSION-$PLATFORM.zip" + $url = "$NODE_MIRROR/$NODE_VERSION/$zipName" + $tempZip = "$env:TEMP\$zipName" + $tempExtract = "$env:TEMP\node-extract-uclaw" + + Log "Downloading $url ..." + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $ProgressPreference = 'SilentlyContinue' + try { + Invoke-WebRequest -Uri $url -OutFile $tempZip -UseBasicParsing + } catch { + try { & curl.exe -sL $url -o $tempZip } catch { LogFail "Node.js download failed"; exit 1 } + } + + if (Test-Path $tempExtract) { Remove-Item -Recurse -Force $tempExtract } + Expand-Archive -Path $tempZip -DestinationPath $tempExtract -Force + $extractedDir = Get-ChildItem $tempExtract | Select-Object -First 1 + New-Item -ItemType Directory -Force -Path $NODE_INSTALL_DIR | Out-Null + Copy-Item -Recurse -Force "$($extractedDir.FullName)\*" $NODE_INSTALL_DIR + Remove-Item -Force $tempZip -ErrorAction SilentlyContinue + Remove-Item -Recurse -Force $tempExtract -ErrorAction SilentlyContinue + + if (Test-Path "$NODE_INSTALL_DIR\node.exe") { + LogOK "Node.js installed" + $INSTALL_NODE = "$NODE_INSTALL_DIR\node.exe" + } else { + LogFail "Node.js install failed"; exit 1 + } + } + $env:PATH = "$NODE_INSTALL_DIR;$env:PATH" +} + +# ============================================================ +# Step 2: OpenClaw bundle +# ============================================================ +Log "Installing OpenClaw ..." + +if (Test-Path "$CORE_DIR\node_modules\openclaw\openclaw.mjs") { + LogOK "OpenClaw already installed" +} else { + $BUNDLE_URL = "https://github.com/dongsheng123132/u-claw/releases/download/v1.0.0-bundle/openclaw-bundle.zip" + $BUNDLE_MIRRORS = @( + "https://ghfast.top/$BUNDLE_URL", + "https://gh-proxy.com/$BUNDLE_URL", + "https://mirror.ghproxy.com/$BUNDLE_URL", + $BUNDLE_URL + ) + $bundleZip = "$env:TEMP\openclaw-bundle.zip" + $downloaded = $false + + foreach ($mirrorUrl in $BUNDLE_MIRRORS) { + Log "Trying: $mirrorUrl" + try { + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $mirrorUrl -OutFile $bundleZip -UseBasicParsing -TimeoutSec 60 + if ((Get-Item $bundleZip).Length -gt 1MB) { $downloaded = $true; break } + } catch {} + } + + if (-not $downloaded) { + try { + & curl.exe -sL "https://ghfast.top/$BUNDLE_URL" -o $bundleZip + if ((Get-Item $bundleZip).Length -gt 1MB) { $downloaded = $true } + } catch {} + } + + if (-not $downloaded) { LogFail "OpenClaw download failed"; exit 1 } + + $tempExtract = "$env:TEMP\openclaw-bundle-extract" + if (Test-Path $tempExtract) { Remove-Item -Recurse -Force $tempExtract } + Expand-Archive -Path $bundleZip -DestinationPath $tempExtract -Force + New-Item -ItemType Directory -Force -Path $CORE_DIR | Out-Null + Copy-Item -Recurse -Force "$tempExtract\*" $CORE_DIR + Remove-Item -Force $bundleZip -ErrorAction SilentlyContinue + Remove-Item -Recurse -Force $tempExtract -ErrorAction SilentlyContinue + + if (Test-Path "$CORE_DIR\node_modules\openclaw\openclaw.mjs") { + LogOK "OpenClaw installed" + } else { + LogFail "OpenClaw install failed"; exit 1 + } +} + +# ============================================================ +# Step 3: Config +# ============================================================ +if ($SkipConfig) { + Log "Skipping config (-SkipConfig)" +} else { + Log "Configuring model: $Model ..." + + # Resolve model preset + $resolvedModel = $Model + $resolvedBaseUrl = $BaseUrl + $resolvedProvider = "custom" + + if ($presets.ContainsKey($Model)) { + $p = $presets[$Model] + if ($p.model) { $resolvedModel = $p.model } + if (-not $resolvedBaseUrl -and $p.baseUrl) { $resolvedBaseUrl = $p.baseUrl } + $resolvedProvider = $p.provider + } + + if ($resolvedProvider -eq "custom" -and $resolvedBaseUrl) { + $configJson = @" +{ + "gateway": { + "mode": "local", + "auth": { "token": "uclaw" } + }, + "agent": { + "model": "$resolvedModel", + "apiKey": "$ApiKey", + "baseUrl": "$resolvedBaseUrl" + } +} +"@ + } else { + $configJson = @" +{ + "gateway": { + "mode": "local", + "auth": { "token": "uclaw" } + }, + "agent": { + "model": "$resolvedModel", + "apiKey": "$ApiKey", + "provider": "$resolvedProvider" + } +} +"@ + } + + [IO.File]::WriteAllText($CONFIG_PATH, $configJson, (New-Object System.Text.UTF8Encoding $false)) + LogOK "Config saved: model=$resolvedModel" +} + +# ============================================================ +# Step 4: Generate start script +# ============================================================ +Log "Generating start.bat ..." + +$startBat = @' +@echo off +setlocal EnableDelayedExpansion +chcp 65001 >nul 2>&1 +title U-Claw + +set "DIR=%~dp0" +set "NODE_BIN=%DIR%runtime\node-win-x64\node.exe" +if not exist "%NODE_BIN%" set "NODE_BIN=node" + +set "OPENCLAW_MJS=%DIR%core\node_modules\openclaw\openclaw.mjs" +set "OPENCLAW_HOME=%DIR%data" +set "OPENCLAW_STATE_DIR=%DIR%data\.openclaw" +set "OPENCLAW_CONFIG_PATH=%DIR%data\.openclaw\openclaw.json" + +set PORT=18789 +:check_port +netstat -an | findstr ":%PORT% " | findstr "LISTENING" >nul 2>&1 +if %errorlevel%==0 ( + set /a PORT+=1 + if !PORT! gtr 18799 (echo No available port & pause & exit /b 1) + goto :check_port +) + +cd /d "%DIR%core" +start /B "" cmd /c "timeout /t 3 /nobreak >nul && start http://127.0.0.1:!PORT!/#token=uclaw" +"%NODE_BIN%" "%OPENCLAW_MJS%" gateway run --allow-unconfigured --force --port !PORT! +pause +'@ + +$startBat | Out-File -Encoding ascii "$UCLAW_DIR\start.bat" +LogOK "start.bat generated" + +# ============================================================ +# Done +# ============================================================ +Log "" +LogOK "Installation complete!" +Log " Location: $UCLAW_DIR" +Log " Start: $UCLAW_DIR\start.bat"