All checks were successful
Tests / test (push) Successful in 1m8s
Sync install scripts and CI container image with NODE_VERSION, regenerate Electron Config.html, and skip gitignored portable/app in origin scan. Co-authored-by: Cursor <cursoragent@cursor.com>
573 lines
22 KiB
PowerShell
573 lines
22 KiB
PowerShell
# ============================================================
|
|
# U-Claw Installer (Windows PowerShell)
|
|
# Usage: irm https://u-claw.org/install.ps1 | iex
|
|
# or: powershell -ExecutionPolicy Bypass -File install.ps1
|
|
# ============================================================
|
|
|
|
# --- Encoding: must run BEFORE any output ---
|
|
Set-ExecutionPolicy Bypass -Scope Process -Force -ErrorAction SilentlyContinue
|
|
try {
|
|
$null = cmd /c chcp 65001
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$PSDefaultParameterValues['Out-File: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.22.3"
|
|
$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 irm|iex pulls skill content from.
|
|
$UCLAW_LOCALE = if ($env:UCLAW_LOCALE) { $env:UCLAW_LOCALE } else { "en" }
|
|
$UCLAW_REF = if ($env:UCLAW_REF) { $env:UCLAW_REF } else { "main" }
|
|
$SKILL_INSTALLER_URL = "https://gitea.fanghe.it.com/zhenghy/u-claw/raw/branch/$UCLAW_REF/lib/install-skills.mjs"
|
|
|
|
# ---- Color helpers ----
|
|
function Write-Green($msg) { Write-Host $msg -ForegroundColor Green }
|
|
function Write-Cyan($msg) { Write-Host $msg -ForegroundColor Cyan }
|
|
function Write-Yellow($msg) { Write-Host $msg -ForegroundColor Yellow }
|
|
function Write-Red($msg) { Write-Host $msg -ForegroundColor Red }
|
|
|
|
# ============================================================
|
|
# Step 1: Banner + System detection
|
|
# ============================================================
|
|
Clear-Host
|
|
Write-Host ""
|
|
Write-Cyan " ==========================================="
|
|
Write-Cyan " U-Claw - AI Assistant Installer (Windows)"
|
|
Write-Cyan " ==========================================="
|
|
Write-Host ""
|
|
|
|
$ARCH = $env:PROCESSOR_ARCHITECTURE
|
|
if ($ARCH -eq "AMD64" -or $ARCH -eq "x86_64") {
|
|
$PLATFORM = "win-x64"
|
|
Write-Green " System: Windows x64"
|
|
} elseif ($ARCH -eq "ARM64") {
|
|
$PLATFORM = "win-arm64"
|
|
Write-Green " System: Windows ARM64"
|
|
} else {
|
|
Write-Red " Unsupported architecture: $ARCH"
|
|
exit 1
|
|
}
|
|
|
|
Write-Host " Install path: $UCLAW_DIR" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# Check existing install
|
|
if (Test-Path "$CORE_DIR\node_modules\openclaw") {
|
|
Write-Yellow " Found existing install: $UCLAW_DIR"
|
|
$overwrite = Read-Host " Overwrite? (y/n) [y]"
|
|
if ($overwrite -eq "n" -or $overwrite -eq "N") {
|
|
Write-Host " Cancelled." -ForegroundColor DarkGray
|
|
exit 0
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
# Create directories
|
|
New-Item -ItemType Directory -Force -Path $RUNTIME_DIR | Out-Null
|
|
New-Item -ItemType Directory -Force -Path $CORE_DIR | Out-Null
|
|
New-Item -ItemType Directory -Force -Path "$DATA_DIR\.openclaw" | Out-Null
|
|
New-Item -ItemType Directory -Force -Path "$DATA_DIR\memory" | Out-Null
|
|
New-Item -ItemType Directory -Force -Path "$DATA_DIR\backups" | Out-Null
|
|
|
|
# ============================================================
|
|
# Step 2: Node.js
|
|
# ============================================================
|
|
Write-Host " [1/6] Install Node.js $NODE_VERSION ..." -ForegroundColor White
|
|
|
|
$NODE_INSTALL_DIR = "$RUNTIME_DIR\node-$PLATFORM"
|
|
$INSTALL_NODE = ""
|
|
$NPM_CLI = ""
|
|
$USE_SYSTEM_NODE = $false
|
|
|
|
# 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) {
|
|
Write-Green " [OK] System Node.js $sysVer found, reusing"
|
|
$INSTALL_NODE = "node"
|
|
# Resolve npm-cli.js next to node.exe; fall back to npm.cmd as a launcher.
|
|
$nodeExeDir = Split-Path $sysNode.Source -Parent
|
|
$candidates = @(
|
|
"$nodeExeDir\node_modules\npm\bin\npm-cli.js",
|
|
"$env:APPDATA\npm\node_modules\npm\bin\npm-cli.js",
|
|
"$env:ProgramFiles\nodejs\node_modules\npm\bin\npm-cli.js"
|
|
)
|
|
$NPM_CLI = $null
|
|
foreach ($p in $candidates) { if (Test-Path $p) { $NPM_CLI = $p; break } }
|
|
if (-not $NPM_CLI) {
|
|
# Last resort: use npm.cmd directly (don't pass through node.exe)
|
|
$NPM_CLI = (Get-Command npm -ErrorAction SilentlyContinue).Source
|
|
}
|
|
$USE_SYSTEM_NODE = $true
|
|
}
|
|
}
|
|
|
|
if (-not $USE_SYSTEM_NODE) {
|
|
if (Test-Path "$NODE_INSTALL_DIR\node.exe") {
|
|
Write-Green " [OK] Node.js already exists, skip download"
|
|
$INSTALL_NODE = "$NODE_INSTALL_DIR\node.exe"
|
|
$NPM_CLI = "$NODE_INSTALL_DIR\node_modules\npm\bin\npm-cli.js"
|
|
} else {
|
|
Write-Cyan " Downloading Node.js $NODE_VERSION ($PLATFORM)..."
|
|
$zipName = "node-$NODE_VERSION-$PLATFORM.zip"
|
|
$url = "$NODE_MIRROR/$NODE_VERSION/$zipName"
|
|
$tempZip = "$env:TEMP\$zipName"
|
|
$tempExtract = "$env:TEMP\node-extract-uclaw"
|
|
|
|
Write-Host " $url"
|
|
try {
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
Invoke-WebRequest -Uri $url -OutFile $tempZip -UseBasicParsing
|
|
} catch {
|
|
try {
|
|
& curl.exe -# -L $url -o $tempZip
|
|
} catch {
|
|
Write-Red " [FAIL] Download failed! Check your network."
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
Write-Host " Extracting..."
|
|
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") {
|
|
Write-Green " [OK] Node.js installed"
|
|
$INSTALL_NODE = "$NODE_INSTALL_DIR\node.exe"
|
|
$NPM_CLI = "$NODE_INSTALL_DIR\node_modules\npm\bin\npm-cli.js"
|
|
} else {
|
|
Write-Red " [FAIL] Node.js install failed"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
$env:PATH = "$NODE_INSTALL_DIR;$env:PATH"
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# Step 3: OpenClaw + QQ plugin (pre-bundled download)
|
|
# ============================================================
|
|
Write-Host " [2/6] Install OpenClaw + QQ plugin ..." -ForegroundColor White
|
|
|
|
if (Test-Path "$CORE_DIR\node_modules\openclaw") {
|
|
Write-Green " [OK] OpenClaw already installed, skip"
|
|
} else {
|
|
Write-Cyan " Downloading pre-bundled package (no npm install needed)..."
|
|
$BUNDLE_GITHUB_URL = "https://gitea.fanghe.it.com/zhenghy/u-claw/releases/download/v1.0.0-bundle/openclaw-bundle.zip"
|
|
$BUNDLE_MIRRORS = @(
|
|
$BUNDLE_GITHUB_URL
|
|
)
|
|
$bundleZip = "$env:TEMP\openclaw-bundle.zip"
|
|
$downloaded = $false
|
|
|
|
foreach ($mirrorUrl in $BUNDLE_MIRRORS) {
|
|
Write-Host " Trying: $mirrorUrl" -ForegroundColor DarkGray
|
|
try {
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
Invoke-WebRequest -Uri $mirrorUrl -OutFile $bundleZip -UseBasicParsing -TimeoutSec 120
|
|
if (Test-Path $bundleZip) {
|
|
$fileSize = (Get-Item $bundleZip).Length
|
|
if ($fileSize -gt 1MB) {
|
|
Write-Host " Downloaded ($([math]::Round($fileSize/1MB,1)) MB)" -ForegroundColor DarkGray
|
|
$downloaded = $true
|
|
break
|
|
} else {
|
|
Write-Host " File too small ($fileSize bytes), trying next..." -ForegroundColor DarkGray
|
|
Remove-Item -Force $bundleZip -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Host " Failed: $($_.Exception.Message.Split([char]10)[0])" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
|
|
if (-not $downloaded) {
|
|
Write-Host " Trying curl fallback..." -ForegroundColor DarkGray
|
|
$curlMirrors = @(
|
|
$BUNDLE_GITHUB_URL
|
|
)
|
|
foreach ($curlUrl in $curlMirrors) {
|
|
try {
|
|
& curl.exe -L --max-time 120 --retry 2 $curlUrl -o $bundleZip 2>$null
|
|
if (Test-Path $bundleZip) {
|
|
if ((Get-Item $bundleZip).Length -gt 1MB) { $downloaded = $true; break }
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
if (-not $downloaded) {
|
|
Write-Red " [FAIL] Download failed! Check your network."
|
|
Write-Yellow " Manual download: $BUNDLE_GITHUB_URL"
|
|
Write-Yellow " Place file at: $bundleZip then re-run installer"
|
|
exit 1
|
|
}
|
|
|
|
# Integrity check. Until this landed the installer only checked that the
|
|
# download was larger than 1MB, then extracted and ran it -- the same
|
|
# "fetch and execute, unverified" shape we warn about elsewhere.
|
|
# The publisher writes <asset>.sha256 next to the bundle; see release.yml.
|
|
$expectedHash = $null
|
|
try {
|
|
$hashBody = (Invoke-WebRequest -Uri "$BUNDLE_GITHUB_URL.sha256" -UseBasicParsing -TimeoutSec 30).Content
|
|
$expectedHash = ($hashBody -split '\s+')[0].Trim().ToLower()
|
|
} catch {
|
|
$expectedHash = $null
|
|
}
|
|
|
|
if ($expectedHash) {
|
|
$actualHash = (Get-FileHash -Path $bundleZip -Algorithm SHA256).Hash.ToLower()
|
|
if ($actualHash -ne $expectedHash) {
|
|
Remove-Item -Force $bundleZip -ErrorAction SilentlyContinue
|
|
Write-Red " [FAIL] The downloaded file does not match its published checksum."
|
|
Write-Yellow " It was damaged in transit, or it is not the file we published."
|
|
Write-Yellow " Nothing was installed. Try again on a different network."
|
|
exit 1
|
|
}
|
|
Write-Green " [OK] Checksum verified"
|
|
} else {
|
|
Write-Yellow " [!] No published checksum found - integrity was not verified."
|
|
}
|
|
|
|
Write-Host " Extracting..." -ForegroundColor Cyan
|
|
$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") {
|
|
Write-Green " [OK] OpenClaw installed"
|
|
} else {
|
|
Write-Red " [FAIL] OpenClaw install failed"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# QQ plugin (included in bundle)
|
|
if (Test-Path "$CORE_DIR\node_modules\@sliverp\qqbot") {
|
|
Write-Green " [OK] QQ plugin included"
|
|
} else {
|
|
Write-Yellow " [WARN] QQ plugin not included (does not affect main features)"
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# Step 4: 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) and keeps
|
|
# non-ASCII bytes out of the launchers.
|
|
Write-Host " [3/6] Installing skills ..." -ForegroundColor White
|
|
|
|
$SKILLS_TARGET = "$CORE_DIR\node_modules\openclaw\skills"
|
|
if (-not (Test-Path $SKILLS_TARGET)) { New-Item -ItemType Directory -Force -Path $SKILLS_TARGET | Out-Null }
|
|
|
|
$skillInstaller = Join-Path $PSScriptRoot "..\lib\install-skills.mjs"
|
|
$skillTemp = $null
|
|
if (-not (Test-Path $skillInstaller)) {
|
|
# irm | iex has no checkout: fetch the installer, then let it fetch content.
|
|
$skillTemp = Join-Path $env:TEMP "uclaw-install-skills.mjs"
|
|
try {
|
|
Invoke-WebRequest -Uri $SKILL_INSTALLER_URL -OutFile $skillTemp -UseBasicParsing -TimeoutSec 60
|
|
$skillInstaller = $skillTemp
|
|
} catch {
|
|
Write-Host " [!] Could not download the skill installer - skipping skills." -ForegroundColor Yellow
|
|
Write-Host " You can add them later from the Skill Hub." -ForegroundColor DarkGray
|
|
$skillInstaller = $null
|
|
}
|
|
}
|
|
|
|
if ($skillInstaller) {
|
|
& $INSTALL_NODE $skillInstaller --target $SKILLS_TARGET --locale $UCLAW_LOCALE --ref $UCLAW_REF
|
|
if ($skillTemp) { Remove-Item -Force $skillTemp -ErrorAction SilentlyContinue }
|
|
}
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# Step 5: Model configuration
|
|
# ============================================================
|
|
Write-Host " [4/6] Configure AI model ..." -ForegroundColor White
|
|
Write-Host ""
|
|
|
|
$hasConfig = (Test-Path $CONFIG_PATH) -and (Select-String -Path $CONFIG_PATH -Pattern "apiKey" -Quiet -ErrorAction SilentlyContinue)
|
|
|
|
if ($hasConfig) {
|
|
Write-Green " [OK] Model already configured, skip"
|
|
} else {
|
|
Write-Host " Select AI model:" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " -- China (direct access, no VPN) --" -ForegroundColor White
|
|
Write-Host " 1) DeepSeek ** Recommended **" -ForegroundColor Green
|
|
Write-Host " 2) Kimi (Moonshot)"
|
|
Write-Host " 3) Qwen (Alibaba)"
|
|
Write-Host " 4) GLM (Zhipu)"
|
|
Write-Host " 5) MiniMax"
|
|
Write-Host " 6) Doubao (Volcengine)"
|
|
Write-Host " 7) SiliconFlow"
|
|
Write-Host ""
|
|
Write-Host " -- International --" -ForegroundColor White
|
|
Write-Host " 8) Claude 9) GPT"
|
|
Write-Host ""
|
|
Write-Host " -- Local --" -ForegroundColor White
|
|
Write-Host " 10) Ollama (local model)"
|
|
Write-Host ""
|
|
|
|
$choice = Read-Host " Enter number [1]"
|
|
if ([string]::IsNullOrEmpty($choice)) { $choice = "1" }
|
|
|
|
$modelConfigs = @{
|
|
"1" = @{ model="deepseek-v4-flash"; baseUrl="https://api.deepseek.com/v1"; provider="custom"; label="DeepSeek API Key"; hint="Get key: https://platform.deepseek.com/api_keys"; needKey=$true }
|
|
"2" = @{ model="moonshot-v1-auto"; baseUrl="https://api.moonshot.cn/v1"; provider="custom"; label="Moonshot API Key"; hint="Get key: https://platform.moonshot.cn/console/api-keys"; needKey=$true }
|
|
"3" = @{ model="qwen-plus"; baseUrl="https://dashscope.aliyuncs.com/compatible-mode/v1"; provider="custom"; label="Qwen API Key"; hint="Get key: https://dashscope.console.aliyun.com/apiKey (free quota available)"; needKey=$true }
|
|
"4" = @{ model="glm-4-plus"; baseUrl="https://open.bigmodel.cn/api/paas/v4"; provider="custom"; label="Zhipu API Key"; hint="Get key: https://open.bigmodel.cn/usercenter/apikeys"; needKey=$true }
|
|
"5" = @{ model="abab6.5s-chat"; baseUrl="https://api.minimax.chat/v1"; provider="custom"; label="MiniMax API Key"; hint="Get key: https://platform.minimaxi.com/"; needKey=$true }
|
|
"6" = @{ model="doubao-pro-256k"; baseUrl="https://ark.cn-beijing.volces.com/api/v3"; provider="custom"; label="Volcengine API Key"; hint="Get key: https://console.volcengine.com/ark"; needKey=$true }
|
|
"7" = @{ model="deepseek-ai/DeepSeek-V3"; baseUrl="https://api.siliconflow.cn/v1"; provider="custom"; label="SiliconFlow API Key"; hint="Get key: https://cloud.siliconflow.cn/account/ak"; needKey=$true }
|
|
"8" = @{ model="claude-sonnet-4-20250514"; baseUrl=""; provider="anthropic"; label="Anthropic API Key"; hint="Get key: https://console.anthropic.com/settings/keys (VPN required)"; needKey=$true }
|
|
"9" = @{ model="gpt-4o"; baseUrl=""; provider="openai"; label="OpenAI API Key"; hint="Get key: https://platform.openai.com/api-keys (VPN required)"; needKey=$true }
|
|
"10" = @{ model="llama3.2"; baseUrl="http://127.0.0.1:11434/v1"; provider="custom"; label=""; hint="Install Ollama first (https://ollama.com), then: ollama run llama3.2"; needKey=$false }
|
|
}
|
|
|
|
$cfg = $modelConfigs[$choice]
|
|
if (-not $cfg) {
|
|
Write-Yellow " Unknown option, using DeepSeek"
|
|
$cfg = $modelConfigs["1"]
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Cyan " $($cfg.hint)"
|
|
Write-Host ""
|
|
|
|
$apiKey = ""
|
|
if ($cfg.needKey) {
|
|
$apiKey = Read-Host " Enter $($cfg.label)"
|
|
if ([string]::IsNullOrEmpty($apiKey)) {
|
|
Write-Yellow " [WARN] No API Key entered. You can configure later via Config.html"
|
|
}
|
|
}
|
|
|
|
# Write config
|
|
if ($cfg.provider -eq "custom" -and $cfg.baseUrl) {
|
|
$providerName = "custom"
|
|
$configJson = @"
|
|
{
|
|
"gateway": {
|
|
"mode": "local",
|
|
"auth": { "token": "uclaw" }
|
|
},
|
|
"models": {
|
|
"mode": "merge",
|
|
"providers": {
|
|
"$providerName": {
|
|
"baseUrl": "$($cfg.baseUrl)",
|
|
"apiKey": "$apiKey",
|
|
"api": "openai-completions",
|
|
"models": [{ "id": "$($cfg.model)" }]
|
|
}
|
|
}
|
|
},
|
|
"agents": { "defaults": { "model": { "primary": "$providerName/$($cfg.model)" } } }
|
|
}
|
|
"@
|
|
} else {
|
|
$providerName = $cfg.provider
|
|
$configJson = @"
|
|
{
|
|
"gateway": {
|
|
"mode": "local",
|
|
"auth": { "token": "uclaw" }
|
|
},
|
|
"models": {
|
|
"mode": "merge",
|
|
"providers": {
|
|
"$providerName": {
|
|
"apiKey": "$apiKey",
|
|
"api": "$(if ($providerName -eq 'anthropic') {'anthropic'} else {'openai-completions'})",
|
|
"models": [{ "id": "$($cfg.model)" }]
|
|
}
|
|
}
|
|
},
|
|
"agents": { "defaults": { "model": { "primary": "$providerName/$($cfg.model)" } } }
|
|
}
|
|
"@
|
|
}
|
|
|
|
[IO.File]::WriteAllText($CONFIG_PATH, $configJson, (New-Object System.Text.UTF8Encoding $false))
|
|
Write-Green " [OK] Model configured: $($cfg.model)"
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# Step 6: Generate start scripts
|
|
# ============================================================
|
|
Write-Host " [5/6] Generate start scripts ..." -ForegroundColor White
|
|
|
|
$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 "OPENCLAW_DISABLE_BONJOUR=1"
|
|
|
|
REM Find available port
|
|
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"
|
|
|
|
Write-Green " [OK] start.bat generated"
|
|
|
|
# Remote help script
|
|
$remoteBat = @'
|
|
@echo off
|
|
chcp 65001 >nul 2>&1
|
|
title U-Claw Remote Help
|
|
echo.
|
|
echo ==========================================
|
|
echo U-Claw Remote Help
|
|
echo ==========================================
|
|
echo.
|
|
echo Connecting...
|
|
powershell -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;$ProgressPreference='SilentlyContinue';iwr 'https://u-claw.org/remote.ps1' -OutFile $env:TEMP\uclaw-remote.ps1;$s=gc $env:TEMP\uclaw-remote.ps1 -Raw -Encoding UTF8;iex $s"
|
|
pause
|
|
'@
|
|
[IO.File]::WriteAllText("$UCLAW_DIR\remote-help.bat", $remoteBat, (New-Object System.Text.ASCIIEncoding))
|
|
|
|
# Uninstall script
|
|
$uninstallBat = @'
|
|
@echo off
|
|
chcp 65001 >nul 2>&1
|
|
title U-Claw Uninstall
|
|
echo.
|
|
echo ==========================================
|
|
echo U-Claw Uninstall
|
|
echo ==========================================
|
|
echo.
|
|
echo Will delete: %USERPROFILE%\.uclaw
|
|
echo.
|
|
set /p confirm= Confirm uninstall? (y/n) [n]:
|
|
if /i not "%confirm%"=="y" (echo Cancelled. & pause & exit /b 0)
|
|
echo Uninstalling...
|
|
rmdir /s /q "%USERPROFILE%\.uclaw"
|
|
echo Done!
|
|
pause
|
|
'@
|
|
[IO.File]::WriteAllText("$UCLAW_DIR\uninstall.bat", $uninstallBat, (New-Object System.Text.ASCIIEncoding))
|
|
|
|
Write-Green " [OK] Remote help + uninstall tools generated"
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# Step 7: Verify
|
|
# ============================================================
|
|
Write-Host " [6/6] Verify installation ..." -ForegroundColor White
|
|
Write-Host ""
|
|
|
|
try {
|
|
$nodeVer = & $INSTALL_NODE --version 2>$null
|
|
Write-Green " [OK] Node.js $nodeVer"
|
|
} catch {
|
|
Write-Red " [FAIL] Node.js"
|
|
}
|
|
|
|
if (Test-Path "$CORE_DIR\node_modules\openclaw\openclaw.mjs") {
|
|
Write-Green " [OK] OpenClaw"
|
|
} else {
|
|
Write-Red " [FAIL] OpenClaw"
|
|
}
|
|
|
|
if (Test-Path "$CORE_DIR\node_modules\@sliverp\qqbot") {
|
|
Write-Green " [OK] QQ plugin"
|
|
} else {
|
|
Write-Yellow " [WARN] QQ plugin (not required)"
|
|
}
|
|
|
|
$installedSkills = (Get-ChildItem -Directory $SKILLS_TARGET -ErrorAction SilentlyContinue).Count
|
|
Write-Green " [OK] China skills ($installedSkills)"
|
|
|
|
if (Test-Path $CONFIG_PATH) {
|
|
Write-Green " [OK] Config file"
|
|
} else {
|
|
Write-Yellow " [WARN] Config (configure after first start)"
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# Summary
|
|
# ============================================================
|
|
$installSize = "{0:N0} MB" -f ((Get-ChildItem -Recurse $UCLAW_DIR -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum / 1MB)
|
|
|
|
Write-Host ""
|
|
Write-Green " ==========================================="
|
|
Write-Green " U-Claw installed successfully!"
|
|
Write-Green " ==========================================="
|
|
Write-Host ""
|
|
Write-Host " Location: $UCLAW_DIR" -ForegroundColor White
|
|
Write-Host " Size: $installSize" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " To start:" -ForegroundColor White
|
|
Write-Host " Double-click $UCLAW_DIR\start.bat" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Write-Host " Browser will open automatically -> start chatting with AI" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " Reconfigure model: edit $CONFIG_PATH" -ForegroundColor DarkGray
|
|
Write-Host " Remote help: double-click $UCLAW_DIR\remote-help.bat" -ForegroundColor DarkGray
|
|
Write-Host " Uninstall: double-click $UCLAW_DIR\uninstall.bat" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host " Press Enter to close..." -ForegroundColor DarkGray
|
|
Read-Host
|