# OpenClaw Windows 安装脚本 # 由 Influo AI 提供支持 # 默认安装方式:git(使用 Influo AI 自托管代码仓库) # 使用方法:powershell -c "irm https://claw.influo-ai.com/install.ps1 | iex" # powershell -c "& ([scriptblock]::Create((irm https://claw.influo-ai.com/install.ps1))) -Tag beta -NoOnboard -DryRun" param( [string]$Tag = "latest", [ValidateSet("npm", "git")] [string]$InstallMethod = "git", [string]$GitDir, [switch]$NoOnboard, [switch]$NoGitUpdate, [switch]$DryRun ) $ErrorActionPreference = "Stop" Write-Host "" Write-Host " OpenClaw 安装程序" -ForegroundColor Cyan Write-Host " 由 Influo AI 提供支持" -ForegroundColor DarkGray Write-Host "" # 检查 PowerShell 版本 if ($PSVersionTable.PSVersion.Major -lt 5) { Write-Host "错误:需要 PowerShell 5 或更高版本" -ForegroundColor Red exit 1 } Write-Host "[OK] 已检测到 Windows 系统" -ForegroundColor Green if (-not $PSBoundParameters.ContainsKey("InstallMethod")) { if (-not [string]::IsNullOrWhiteSpace($env:OPENCLAW_INSTALL_METHOD)) { $InstallMethod = $env:OPENCLAW_INSTALL_METHOD } } if (-not $PSBoundParameters.ContainsKey("GitDir")) { if (-not [string]::IsNullOrWhiteSpace($env:OPENCLAW_GIT_DIR)) { $GitDir = $env:OPENCLAW_GIT_DIR } } if (-not $PSBoundParameters.ContainsKey("NoOnboard")) { if ($env:OPENCLAW_NO_ONBOARD -eq "1") { $NoOnboard = $true } } if (-not $PSBoundParameters.ContainsKey("NoGitUpdate")) { if ($env:OPENCLAW_GIT_UPDATE -eq "0") { $NoGitUpdate = $true } } if (-not $PSBoundParameters.ContainsKey("DryRun")) { if ($env:OPENCLAW_DRY_RUN -eq "1") { $DryRun = $true } } if ([string]::IsNullOrWhiteSpace($GitDir)) { $userHome = [Environment]::GetFolderPath("UserProfile") $GitDir = (Join-Path $userHome "openclaw") } function Get-SettingOrDefault { param( [string]$EnvName, [string]$DefaultValue ) $envValue = [Environment]::GetEnvironmentVariable($EnvName) if (-not [string]::IsNullOrWhiteSpace($envValue)) { return $envValue } return $DefaultValue } function Join-Url { param( [string]$Base, [string]$Path ) if ([string]::IsNullOrWhiteSpace($Base)) { return $Path } return ($Base.TrimEnd("/") + "/" + $Path.TrimStart("/")) } function Get-UrlLeafName { param( [string]$Url ) if ([string]::IsNullOrWhiteSpace($Url)) { return $null } try { $uri = [System.Uri]$Url $leaf = [System.IO.Path]::GetFileName($uri.AbsolutePath) if (-not [string]::IsNullOrWhiteSpace($leaf)) { return $leaf } } catch { } return ($Url.TrimEnd("/") -split "/")[-1] } function Get-UniqueStringValues { param( [string[]]$Values ) return @($Values | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique) } function Get-DownloadUrlCandidates { param( [string]$PrimaryUrl, [string[]]$FallbackUrls ) return (Get-UniqueStringValues -Values (@($PrimaryUrl) + @($FallbackUrls))) } function Invoke-WebRequestWithFallback { param( [Parameter(Mandatory = $true)] [string[]]$Urls, [Parameter(Mandatory = $true)] [string]$OutFile, [Parameter(Mandatory = $true)] [string]$Label ) $attemptedUrls = @() $lastErrorMessage = $null foreach ($url in (Get-UniqueStringValues -Values $Urls)) { $attemptedUrls += $url try { if (Test-Path $OutFile) { Remove-Item -Force $OutFile } Invoke-WebRequest -Uri $url -OutFile $OutFile return $url } catch { $lastErrorMessage = $_.Exception.Message } } throw "$Label 下载失败。已尝试:$($attemptedUrls -join '、')。最后一次错误:$lastErrorMessage" } function Get-ExpandedArchiveContentRoot { param( [Parameter(Mandatory = $true)] [string]$ExtractDir ) $entries = @(Get-ChildItem -Force -Path $ExtractDir) if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) { return $entries[0].FullName } return $ExtractDir } function Move-ExpandedArchiveContents { param( [Parameter(Mandatory = $true)] [string]$ExtractDir, [Parameter(Mandatory = $true)] [string]$DestinationDir ) $contentRoot = Get-ExpandedArchiveContentRoot -ExtractDir $ExtractDir foreach ($entry in (Get-ChildItem -Force -Path $contentRoot)) { Move-Item -Path $entry.FullName -Destination $DestinationDir -Force } } $OpenClawSiteBaseUrl = Get-SettingOrDefault -EnvName "OPENCLAW_SITE_BASE_URL" -DefaultValue "https://claw.influo-ai.com" $OpenClawMirrorBaseUrl = Get-SettingOrDefault -EnvName "OPENCLAW_DOWNLOAD_BASE_URL" -DefaultValue (Join-Url -Base $OpenClawSiteBaseUrl -Path "install-assets/windows") $OpenClawLegacyMirrorBaseUrl = $OpenClawSiteBaseUrl $OpenClawNodeZipName = "node-v22.22.1-win-x64.zip" $OpenClawGitZipName = "MinGit-2.53.0.2-64-bit.zip" $OpenClawNodeZipUrl = Get-SettingOrDefault -EnvName "OPENCLAW_NODE_ZIP_URL" -DefaultValue (Join-Url -Base $OpenClawMirrorBaseUrl -Path $OpenClawNodeZipName) $OpenClawNodeZipFallbackUrls = Get-DownloadUrlCandidates -PrimaryUrl (Join-Url -Base $OpenClawLegacyMirrorBaseUrl -Path $OpenClawNodeZipName) -FallbackUrls @() $OpenClawNodeZipLabel = Get-SettingOrDefault -EnvName "OPENCLAW_NODE_ZIP_LABEL" -DefaultValue "Influo AI Node.js 22 Windows 64 位安装包" $OpenClawGitZipUrl = Get-SettingOrDefault -EnvName "OPENCLAW_GIT_ZIP_URL" -DefaultValue (Join-Url -Base $OpenClawMirrorBaseUrl -Path $OpenClawGitZipName) $OpenClawGitZipFallbackUrls = Get-DownloadUrlCandidates -PrimaryUrl (Join-Url -Base $OpenClawLegacyMirrorBaseUrl -Path $OpenClawGitZipName) -FallbackUrls @() $OpenClawGitZipLabel = Get-SettingOrDefault -EnvName "OPENCLAW_GIT_ZIP_LABEL" -DefaultValue "Influo AI Git Windows 64 位安装包" $OpenClawGitRepoUrl = Get-SettingOrDefault -EnvName "OPENCLAW_GIT_REPO_URL" -DefaultValue "https://claw.influo-ai.com/openclaw.git" $OpenClawNpmRegistry = "https://registry.npmmirror.com" $env:NPM_CONFIG_REGISTRY = $OpenClawNpmRegistry $env:COREPACK_NPM_REGISTRY = $OpenClawNpmRegistry function Get-DependencyRoot { $base = Join-Path $env:LOCALAPPDATA "OpenClaw\deps" New-Item -ItemType Directory -Force -Path $base | Out-Null return $base } function Get-PortableNodeRoot { return (Join-Path (Get-DependencyRoot) "portable-node") } function Get-PortableNodeCommandPath { $root = Get-PortableNodeRoot if (-not (Test-Path $root)) { return $null } foreach ($candidate in @( (Join-Path $root "node.exe"), (Join-Path $root "bin\node.exe") )) { if (Test-Path $candidate) { return $candidate } } $nodeExe = Get-ChildItem -Path $root -Filter "node.exe" -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if ($nodeExe) { return $nodeExe.FullName } return $null } function Use-PortableNodeIfPresent { $nodeExe = Get-PortableNodeCommandPath if (-not $nodeExe) { return $false } Add-ToProcessPath (Split-Path -Parent $nodeExe) return $true } function Install-PortableNode { $nodeRoot = Get-PortableNodeRoot $tmpZip = Join-Path $env:TEMP (Get-UrlLeafName -Url $OpenClawNodeZipUrl) $tmpExtract = Join-Path $env:TEMP ("openclaw-portable-node-" + [guid]::NewGuid().ToString("N")) if (Test-Path $nodeRoot) { Remove-Item -Recurse -Force $nodeRoot } if (Test-Path $tmpExtract) { Remove-Item -Recurse -Force $tmpExtract } New-Item -ItemType Directory -Force -Path $nodeRoot | Out-Null New-Item -ItemType Directory -Force -Path $tmpExtract | Out-Null try { Write-Host " 正在下载 $OpenClawNodeZipLabel..." -ForegroundColor Gray $downloadUrl = Invoke-WebRequestWithFallback -Urls (Get-DownloadUrlCandidates -PrimaryUrl $OpenClawNodeZipUrl -FallbackUrls $OpenClawNodeZipFallbackUrls) -OutFile $tmpZip -Label $OpenClawNodeZipLabel Expand-Archive -Path $tmpZip -DestinationPath $tmpExtract -Force Move-ExpandedArchiveContents -ExtractDir $tmpExtract -DestinationDir $nodeRoot Write-Host " 已从 $downloadUrl 下载安装包" -ForegroundColor DarkGray } finally { if (Test-Path $tmpZip) { Remove-Item -Force $tmpZip } if (Test-Path $tmpExtract) { Remove-Item -Recurse -Force $tmpExtract } } if (-not (Use-PortableNodeIfPresent)) { throw "Node.js 下载完成,但当前仍无法使用。" } } # 检查是否已安装 Node.js function Check-Node { $null = Use-PortableNodeIfPresent try { $nodeVersion = (node -v 2>$null) if ($nodeVersion) { $version = [int]($nodeVersion -replace 'v(\d+)\..*', '$1') if ($version -ge 22) { Write-Host "[OK] 已找到 Node.js $nodeVersion" -ForegroundColor Green return $true } else { Write-Host "[!] 已找到 Node.js $nodeVersion,但需要 22 或更高版本" -ForegroundColor Yellow return $false } } } catch { Write-Host "[!] 未找到 Node.js" -ForegroundColor Yellow return $false } return $false } # 安装 Node.js function Install-Node { Write-Host "[*] 正在安装 Node.js..." -ForegroundColor Yellow try { Install-PortableNode if (Check-Node) { Write-Host "[OK] 已从 Influo AI 安装镜像安装 Node.js" -ForegroundColor Green return } } catch { Write-Host "[!] 从 Influo AI 安装镜像安装 Node.js 失败:$($_.Exception.Message)" -ForegroundColor Yellow } # 先尝试使用 winget(Windows 11 或已安装应用安装程序的 Windows 10) if (Get-Command winget -ErrorAction SilentlyContinue) { Write-Host " 正在使用 winget 安装..." -ForegroundColor Gray winget install OpenJS.NodeJS.LTS --source winget --accept-package-agreements --accept-source-agreements # 刷新当前窗口的 PATH $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") if (Check-Node) { Write-Host "[OK] 已通过 winget 安装 Node.js" -ForegroundColor Green return } Write-Host "[!] winget 已执行完成,但当前窗口还无法使用 Node.js" -ForegroundColor Yellow Write-Host "如果 Node.js 已安装成功,请关闭并重新打开 PowerShell 后再次运行安装脚本。" -ForegroundColor Yellow exit 1 } # 尝试使用 Chocolatey if (Get-Command choco -ErrorAction SilentlyContinue) { Write-Host " 正在使用 Chocolatey 安装..." -ForegroundColor Gray choco install nodejs-lts -y # 刷新当前窗口的 PATH $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Write-Host "[OK] 已通过 Chocolatey 安装 Node.js" -ForegroundColor Green return } # 尝试使用 Scoop if (Get-Command scoop -ErrorAction SilentlyContinue) { Write-Host " 正在使用 Scoop 安装..." -ForegroundColor Gray scoop install nodejs-lts Write-Host "[OK] 已通过 Scoop 安装 Node.js" -ForegroundColor Green return } # 如果以上方式都不可用,则提示手动下载安装 Write-Host "" Write-Host "错误:无法自动安装 Node.js。" -ForegroundColor Red Write-Host "" Write-Host "请联系 Influo AI 支持团队检查 Node.js 安装包是否已准备好:" -ForegroundColor Yellow Write-Host " $OpenClawNodeZipUrl" -ForegroundColor Cyan Write-Host "" Write-Host "如需人工处理,也可以单独安装 Node.js 22 或更高版本后再重试。" -ForegroundColor Gray exit 1 } # 检查是否已经安装过 OpenClaw function Check-ExistingOpenClaw { if (Get-OpenClawCommandPath) { Write-Host "[*] 检测到已安装的 OpenClaw" -ForegroundColor Yellow return $true } return $false } function Check-Git { try { $null = Get-Command git -ErrorAction Stop return $true } catch { return $false } } function Add-ToProcessPath { param( [Parameter(Mandatory = $true)] [string]$PathEntry ) if ([string]::IsNullOrWhiteSpace($PathEntry)) { return } $currentEntries = @($env:Path -split ";" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($currentEntries | Where-Object { $_ -ieq $PathEntry }) { return } $env:Path = "$PathEntry;$env:Path" } function Convert-NativeOutputToText { param( [object[]]$Items ) $lines = @() foreach ($item in $Items) { if ($null -eq $item) { continue } if ($item -is [System.Management.Automation.ErrorRecord]) { $text = $item.ToString() } else { $text = [string]$item } if (-not [string]::IsNullOrWhiteSpace($text)) { $lines += $text } } return $lines } function Invoke-NativeCommandCapture { param( [Parameter(Mandatory = $true)] [string]$CommandPath, [string[]]$Arguments ) $prevErrorActionPreference = $ErrorActionPreference $rawOutput = @() $exitCode = 0 try { $ErrorActionPreference = "Continue" $rawOutput = & $CommandPath @Arguments 2>&1 if ($null -ne $LASTEXITCODE) { $exitCode = $LASTEXITCODE } } finally { $ErrorActionPreference = $prevErrorActionPreference } $outputLines = @(Convert-NativeOutputToText -Items $rawOutput) return @{ ExitCode = $exitCode OutputLines = $outputLines OutputText = ($outputLines -join "`r`n") } } function Test-OutputIndicatesMissingGit { param( [string]$OutputText ) if ([string]::IsNullOrWhiteSpace($OutputText)) { return $false } return ($OutputText -match "spawn git" -or $OutputText -match "ENOENT.*git" -or $OutputText -match "not found: git" -or $OutputText -match "'git' is not recognized") } function Test-OutputIndicatesNativeDependencyIssue { param( [string]$OutputText ) if ([string]::IsNullOrWhiteSpace($OutputText)) { return $false } return ($OutputText -match "node-llama-cpp" -or $OutputText -match "matrix-sdk-crypto-nodejs" -or $OutputText -match "ELIFECYCLE") } function Get-PortableGitRoot { return (Join-Path (Get-DependencyRoot) "portable-git") } function Get-PortableGitCommandPath { $root = Get-PortableGitRoot foreach ($candidate in @( (Join-Path $root "mingw64\bin\git.exe"), (Join-Path $root "cmd\git.exe"), (Join-Path $root "bin\git.exe"), (Join-Path $root "git.exe") )) { if (Test-Path $candidate) { return $candidate } } return $null } function Use-PortableGitIfPresent { $gitExe = Get-PortableGitCommandPath if (-not $gitExe) { return $false } $portableRoot = Get-PortableGitRoot foreach ($pathEntry in @( (Join-Path $portableRoot "mingw64\bin"), (Join-Path $portableRoot "usr\bin"), (Split-Path -Parent $gitExe) )) { if (Test-Path $pathEntry) { Add-ToProcessPath $pathEntry } } if (Check-Git) { return $true } return $false } function Resolve-PortableGitDownload { return @{ Tag = $OpenClawGitZipLabel Name = (Get-UrlLeafName -Url $OpenClawGitZipUrl) Url = $OpenClawGitZipUrl FallbackUrls = $OpenClawGitZipFallbackUrls } } function Install-PortableGit { if (Use-PortableGitIfPresent) { $portableVersion = (& git --version 2>$null) if ($portableVersion) { Write-Host "[OK] 当前用户目录下已可用 Git:$portableVersion" -ForegroundColor Green } return } Write-Host "[*] 未找到 Git,正在为当前用户准备便携版 Git..." -ForegroundColor Yellow $download = Resolve-PortableGitDownload $portableRoot = Get-PortableGitRoot $portableParent = Split-Path -Parent $portableRoot $tmpZip = Join-Path $env:TEMP $download.Name $tmpExtract = Join-Path $env:TEMP ("openclaw-portable-git-" + [guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Force -Path $portableParent | Out-Null if (Test-Path $portableRoot) { Remove-Item -Recurse -Force $portableRoot } if (Test-Path $tmpExtract) { Remove-Item -Recurse -Force $tmpExtract } New-Item -ItemType Directory -Force -Path $tmpExtract | Out-Null try { Write-Host " 正在下载 $($download.Tag)..." -ForegroundColor Gray $downloadUrl = Invoke-WebRequestWithFallback -Urls (Get-DownloadUrlCandidates -PrimaryUrl $download.Url -FallbackUrls $download.FallbackUrls) -OutFile $tmpZip -Label $download.Tag Expand-Archive -Path $tmpZip -DestinationPath $tmpExtract -Force Move-ExpandedArchiveContents -ExtractDir $tmpExtract -DestinationDir $portableRoot Write-Host " 已从 $downloadUrl 下载安装包" -ForegroundColor DarkGray } finally { if (Test-Path $tmpZip) { Remove-Item -Force $tmpZip } if (Test-Path $tmpExtract) { Remove-Item -Recurse -Force $tmpExtract } } if (-not (Use-PortableGitIfPresent)) { throw "便携版 Git 准备完成,但当前仍无法使用 git。" } $portableVersion = (& git --version 2>$null) Write-Host "[OK] 当前用户可用的 Git 已准备完成:$portableVersion" -ForegroundColor Green } function Ensure-Git { if (Check-Git) { return } if (Use-PortableGitIfPresent) { return } try { Install-PortableGit if (Check-Git) { return } } catch { Write-Host "[!] 便携版 Git 准备失败:$($_.Exception.Message)" -ForegroundColor Yellow } Write-Host "" Write-Host "错误:安装 OpenClaw 需要 Git。" -ForegroundColor Red Write-Host "自动准备当前用户可用的 Git 未成功。" -ForegroundColor Yellow Write-Host "请联系 Influo AI 支持团队检查 Git 安装包是否已准备好:" -ForegroundColor Yellow Write-Host " $OpenClawGitZipUrl" -ForegroundColor Cyan exit 1 } function Get-OpenClawCommandPath { $openclawCmd = Get-Command openclaw.cmd -ErrorAction SilentlyContinue if ($openclawCmd -and $openclawCmd.Source) { return $openclawCmd.Source } $openclaw = Get-Command openclaw -ErrorAction SilentlyContinue if ($openclaw -and $openclaw.Source) { return $openclaw.Source } return $null } function Invoke-OpenClawCommand { param( [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments ) $commandPath = Get-OpenClawCommandPath if (-not $commandPath) { throw "在 PATH 中找不到 openclaw 命令。" } & $commandPath @Arguments } function Resolve-CommandPath { param( [Parameter(Mandatory = $true)] [string[]]$Candidates ) foreach ($candidate in $Candidates) { $command = Get-Command $candidate -ErrorAction SilentlyContinue if ($command -and $command.Source) { return $command.Source } } return $null } function Get-NpmCommandPath { $path = Resolve-CommandPath -Candidates @("npm.cmd", "npm.exe", "npm") if (-not $path) { throw "在 PATH 中找不到 npm。" } return $path } function Get-CorepackCommandPath { return (Resolve-CommandPath -Candidates @("corepack.cmd", "corepack.exe", "corepack")) } function Get-PnpmCommandPath { return (Resolve-CommandPath -Candidates @("pnpm.cmd", "pnpm.exe", "pnpm")) } function Get-NpmGlobalBinCandidates { param( [string]$NpmPrefix ) $candidates = @() if (-not [string]::IsNullOrWhiteSpace($NpmPrefix)) { $candidates += $NpmPrefix $candidates += (Join-Path $NpmPrefix "bin") } if (-not [string]::IsNullOrWhiteSpace($env:APPDATA)) { $candidates += (Join-Path $env:APPDATA "npm") } return $candidates | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique } function Ensure-OpenClawOnPath { if (Get-OpenClawCommandPath) { return $true } $npmPrefix = $null try { $npmPrefix = (& (Get-NpmCommandPath) config get prefix 2>$null).Trim() } catch { $npmPrefix = $null } $npmBins = Get-NpmGlobalBinCandidates -NpmPrefix $npmPrefix foreach ($npmBin in $npmBins) { if (-not (Test-Path (Join-Path $npmBin "openclaw.cmd"))) { continue } $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if (-not ($userPath -split ";" | Where-Object { $_ -ieq $npmBin })) { [Environment]::SetEnvironmentVariable("Path", "$userPath;$npmBin", "User") $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Write-Host "[!] 已将 $npmBin 添加到当前用户的 PATH(如果命令仍不可用,请重开终端)" -ForegroundColor Yellow } return $true } Write-Host "[!] 当前还无法直接运行 openclaw 命令。" -ForegroundColor Yellow Write-Host "请重新打开 PowerShell,或将 npm 的全局安装目录手动加入 PATH。" -ForegroundColor Yellow if ($npmBins.Count -gt 0) { Write-Host "可以检查下面这些目录是否已加入 PATH:" -ForegroundColor Gray foreach ($npmBin in $npmBins) { Write-Host " $npmBin" -ForegroundColor Cyan } } else { Write-Host "提示:可以运行 \"npm config get prefix\" 查看 npm 的全局安装目录。" -ForegroundColor Gray } return $false } function Ensure-Pnpm { if (Get-PnpmCommandPath) { return } $corepackCommand = Get-CorepackCommandPath if ($corepackCommand) { try { & $corepackCommand enable | Out-Null & $corepackCommand prepare pnpm@latest --activate | Out-Null if (Get-PnpmCommandPath) { Write-Host "[OK] 已通过 corepack 安装 pnpm" -ForegroundColor Green return } } catch { # 如果失败,则继续尝试用 npm 安装 } } Write-Host "[*] 正在安装 pnpm..." -ForegroundColor Yellow $prevScriptShell = $env:NPM_CONFIG_SCRIPT_SHELL Remove-Item Env:NPM_CONFIG_SCRIPT_SHELL -ErrorAction SilentlyContinue try { & (Get-NpmCommandPath) install -g pnpm } finally { if ([string]::IsNullOrWhiteSpace($prevScriptShell)) { Remove-Item Env:NPM_CONFIG_SCRIPT_SHELL -ErrorAction SilentlyContinue } else { $env:NPM_CONFIG_SCRIPT_SHELL = $prevScriptShell } } Write-Host "[OK] pnpm 安装完成" -ForegroundColor Green } # 安装 OpenClaw function Install-OpenClaw { if ([string]::IsNullOrWhiteSpace($Tag)) { $Tag = "latest" } # beta 和稳定版目前都使用 openclaw 包 $packageName = "openclaw" if ($Tag -eq "beta" -or $Tag -match "^beta\.") { $packageName = "openclaw" } Write-Host "[*] 正在安装 OpenClaw($packageName@$Tag)..." -ForegroundColor Yellow $prevLogLevel = $env:NPM_CONFIG_LOGLEVEL $prevUpdateNotifier = $env:NPM_CONFIG_UPDATE_NOTIFIER $prevFund = $env:NPM_CONFIG_FUND $prevAudit = $env:NPM_CONFIG_AUDIT $prevScriptShell = $env:NPM_CONFIG_SCRIPT_SHELL $prevNodeLlamaSkipDownload = $env:NODE_LLAMA_CPP_SKIP_DOWNLOAD $env:NPM_CONFIG_LOGLEVEL = "error" $env:NPM_CONFIG_UPDATE_NOTIFIER = "false" $env:NPM_CONFIG_FUND = "false" $env:NPM_CONFIG_AUDIT = "false" Remove-Item Env:NPM_CONFIG_SCRIPT_SHELL -ErrorAction SilentlyContinue $env:NODE_LLAMA_CPP_SKIP_DOWNLOAD = "1" try { $npmCommand = Get-NpmCommandPath $npmResult = Invoke-NativeCommandCapture -CommandPath $npmCommand -Arguments @("install", "-g", "$packageName@$Tag") if ($npmResult.ExitCode -ne 0 -and (Test-OutputIndicatesMissingGit -OutputText $npmResult.OutputText)) { Write-Host "[!] 当前安装包仍需要 Git,正在自动准备后重试..." -ForegroundColor Yellow Ensure-Git $npmResult = Invoke-NativeCommandCapture -CommandPath $npmCommand -Arguments @("install", "-g", "$packageName@$Tag") } if ($npmResult.ExitCode -ne 0) { Write-Host "[!] npm 安装失败" -ForegroundColor Red if (Test-OutputIndicatesMissingGit -OutputText $npmResult.OutputText) { Write-Host "错误:PATH 中找不到 git。" -ForegroundColor Red Write-Host "这通常表示当前安装包仍依赖 Git,请联系 Influo AI 支持团队检查安装包或 Git 镜像:" -ForegroundColor Yellow Write-Host " $OpenClawGitZipUrl" -ForegroundColor Cyan } elseif ($npmResult.OutputText -match "ENOENT") { Write-Host "错误:npm 提示有文件或目录不存在。" -ForegroundColor Red Write-Host "这通常表示安装包内容不完整,或 npm 缓存 / 临时目录异常。" -ForegroundColor Yellow } else { Write-Host "如需查看完整报错,请重新运行安装命令:" -ForegroundColor Yellow Write-Host ' powershell -c "irm https://claw.influo-ai.com/install.ps1 | iex"' -ForegroundColor Cyan } if ($npmResult.OutputLines.Count -gt 0) { $npmResult.OutputLines | ForEach-Object { Write-Host $_ } } exit 1 } } finally { $env:NPM_CONFIG_LOGLEVEL = $prevLogLevel $env:NPM_CONFIG_UPDATE_NOTIFIER = $prevUpdateNotifier $env:NPM_CONFIG_FUND = $prevFund $env:NPM_CONFIG_AUDIT = $prevAudit if ([string]::IsNullOrWhiteSpace($prevScriptShell)) { Remove-Item Env:NPM_CONFIG_SCRIPT_SHELL -ErrorAction SilentlyContinue } else { $env:NPM_CONFIG_SCRIPT_SHELL = $prevScriptShell } $env:NODE_LLAMA_CPP_SKIP_DOWNLOAD = $prevNodeLlamaSkipDownload } Write-Host "[OK] OpenClaw 安装完成" -ForegroundColor Green } # 从 Git 仓库安装 OpenClaw function Install-OpenClawFromGit { param( [string]$RepoDir, [switch]$SkipUpdate ) Ensure-Git Ensure-Pnpm $repoUrl = $OpenClawGitRepoUrl Write-Host "[*] 正在从代码仓库安装 OpenClaw($repoUrl)..." -ForegroundColor Yellow if (-not (Test-Path $RepoDir)) { git clone $repoUrl $RepoDir } if (-not $SkipUpdate) { if (-not (git -C $RepoDir status --porcelain 2>$null)) { git -C $RepoDir pull --rebase 2>$null } else { Write-Host "[!] 本地代码目录有未提交内容,已跳过 git pull" -ForegroundColor Yellow } } else { Write-Host "[!] 已关闭 Git 更新,跳过 git pull" -ForegroundColor Yellow } Remove-LegacySubmodule -RepoDir $RepoDir $prevPnpmScriptShell = $env:NPM_CONFIG_SCRIPT_SHELL $prevPnpmRegistry = $env:PNPM_CONFIG_REGISTRY $prevNodeLlamaSkipDownload = $env:NODE_LLAMA_CPP_SKIP_DOWNLOAD $pnpmCommand = Get-PnpmCommandPath if (-not $pnpmCommand) { throw "pnpm 安装完成后仍未找到可执行命令。" } Remove-Item Env:NPM_CONFIG_SCRIPT_SHELL -ErrorAction SilentlyContinue $env:PNPM_CONFIG_REGISTRY = $OpenClawNpmRegistry $env:NODE_LLAMA_CPP_SKIP_DOWNLOAD = "1" try { $installResult = Invoke-NativeCommandCapture -CommandPath $pnpmCommand -Arguments @("-C", $RepoDir, "install") if ($installResult.ExitCode -ne 0) { Write-Host "[!] pnpm 安装依赖失败" -ForegroundColor Red Write-Host "当前 git 安装链路会通过 pnpm 从源码安装依赖,并使用这个镜像:" -ForegroundColor Yellow Write-Host " $OpenClawNpmRegistry" -ForegroundColor Cyan if (Test-OutputIndicatesNativeDependencyIssue -OutputText $installResult.OutputText) { Write-Host "看起来是原生依赖的 postinstall / build 步骤失败了。" -ForegroundColor Yellow Write-Host "这类报错常见于 node-llama-cpp、matrix-sdk-crypto-nodejs、sharp 等依赖。" -ForegroundColor Yellow Write-Host "脚本已关闭 node-llama-cpp 的自动下载,但其他原生依赖仍可能因为系统环境或二进制获取失败而中断安装。" -ForegroundColor Yellow } if ($installResult.OutputLines.Count -gt 0) { $installResult.OutputLines | ForEach-Object { Write-Host $_ } } exit 1 } $uiBuildResult = Invoke-NativeCommandCapture -CommandPath $pnpmCommand -Arguments @("-C", $RepoDir, "ui:build") if ($uiBuildResult.ExitCode -ne 0) { Write-Host "[!] 图形界面构建失败,继续安装(命令行功能可能仍可用)" -ForegroundColor Yellow if ($uiBuildResult.OutputLines.Count -gt 0) { $uiBuildResult.OutputLines | ForEach-Object { Write-Host $_ } } } $buildResult = Invoke-NativeCommandCapture -CommandPath $pnpmCommand -Arguments @("-C", $RepoDir, "build") if ($buildResult.ExitCode -ne 0) { Write-Host "[!] OpenClaw 源码构建失败" -ForegroundColor Red if ($buildResult.OutputLines.Count -gt 0) { $buildResult.OutputLines | ForEach-Object { Write-Host $_ } } exit 1 } } finally { if ([string]::IsNullOrWhiteSpace($prevPnpmScriptShell)) { Remove-Item Env:NPM_CONFIG_SCRIPT_SHELL -ErrorAction SilentlyContinue } else { $env:NPM_CONFIG_SCRIPT_SHELL = $prevPnpmScriptShell } $env:PNPM_CONFIG_REGISTRY = $prevPnpmRegistry $env:NODE_LLAMA_CPP_SKIP_DOWNLOAD = $prevNodeLlamaSkipDownload } $binDir = Join-Path $env:USERPROFILE ".local\\bin" if (-not (Test-Path $binDir)) { New-Item -ItemType Directory -Force -Path $binDir | Out-Null } $cmdPath = Join-Path $binDir "openclaw.cmd" $cmdContents = "@echo off`r`nnode ""$RepoDir\\dist\\entry.js"" %*`r`n" Set-Content -Path $cmdPath -Value $cmdContents -NoNewline $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if (-not ($userPath -split ";" | Where-Object { $_ -ieq $binDir })) { [Environment]::SetEnvironmentVariable("Path", "$userPath;$binDir", "User") $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Write-Host "[!] 已将 $binDir 添加到当前用户的 PATH(如果命令仍不可用,请重开终端)" -ForegroundColor Yellow } Write-Host "[OK] OpenClaw 启动命令已安装到 $cmdPath" -ForegroundColor Green Write-Host "[i] 这个源码目录使用 pnpm 管理依赖。安装依赖请运行 pnpm install,不要在仓库里运行 npm install。" -ForegroundColor Gray } # 运行 doctor 完成配置迁移(安全、无需交互) function Run-Doctor { Write-Host "[*] 正在运行 doctor 迁移设置..." -ForegroundColor Yellow try { Invoke-OpenClawCommand doctor --non-interactive } catch { # 忽略 doctor 的报错,避免中断安装流程 } Write-Host "[OK] 设置迁移完成" -ForegroundColor Green } function Test-GatewayServiceLoaded { try { $statusJson = (Invoke-OpenClawCommand daemon status --json 2>$null) if ([string]::IsNullOrWhiteSpace($statusJson)) { return $false } $parsed = $statusJson | ConvertFrom-Json if ($parsed -and $parsed.service -and $parsed.service.loaded) { return $true } } catch { return $false } return $false } function Refresh-GatewayServiceIfLoaded { if (-not (Get-OpenClawCommandPath)) { return } if (-not (Test-GatewayServiceLoaded)) { return } Write-Host "[*] 正在刷新已加载的网关服务..." -ForegroundColor Yellow try { Invoke-OpenClawCommand gateway install --force | Out-Null } catch { Write-Host "[!] 网关服务刷新失败,继续后续流程。" -ForegroundColor Yellow return } try { Invoke-OpenClawCommand gateway restart | Out-Null Invoke-OpenClawCommand gateway status --probe --json | Out-Null Write-Host "[OK] 网关服务已刷新" -ForegroundColor Green } catch { Write-Host "[!] 网关服务重启失败,继续后续流程。" -ForegroundColor Yellow } } function Get-LegacyRepoDir { if (-not [string]::IsNullOrWhiteSpace($env:OPENCLAW_GIT_DIR)) { return $env:OPENCLAW_GIT_DIR } $userHome = [Environment]::GetFolderPath("UserProfile") return (Join-Path $userHome "openclaw") } function Remove-LegacySubmodule { param( [string]$RepoDir ) if ([string]::IsNullOrWhiteSpace($RepoDir)) { $RepoDir = Get-LegacyRepoDir } $legacyDir = Join-Path $RepoDir "Peekaboo" if (Test-Path $legacyDir) { Write-Host "[!] 正在删除旧版子模块目录:$legacyDir" -ForegroundColor Yellow Remove-Item -Recurse -Force $legacyDir } } # 主安装流程 function Main { if ($InstallMethod -ne "npm" -and $InstallMethod -ne "git") { Write-Host "错误:-InstallMethod 参数无效,请使用 npm 或 git。" -ForegroundColor Red exit 2 } if ($DryRun) { Write-Host "[OK] 当前为演练模式,不会实际安装" -ForegroundColor Green Write-Host "[OK] 安装方式:$InstallMethod" -ForegroundColor Green Write-Host "[OK] npm 镜像:$OpenClawNpmRegistry" -ForegroundColor Green Write-Host "[OK] Node.js 镜像:$OpenClawNodeZipUrl" -ForegroundColor Green foreach ($fallbackUrl in $OpenClawNodeZipFallbackUrls) { if ($fallbackUrl -ne $OpenClawNodeZipUrl) { Write-Host "[OK] Node.js 备用镜像:$fallbackUrl" -ForegroundColor Green } } if ($InstallMethod -eq "git") { Write-Host "[OK] Git 安装目录:$GitDir" -ForegroundColor Green Write-Host "[OK] Git 镜像:$OpenClawGitZipUrl" -ForegroundColor Green foreach ($fallbackUrl in $OpenClawGitZipFallbackUrls) { if ($fallbackUrl -ne $OpenClawGitZipUrl) { Write-Host "[OK] Git 备用镜像:$fallbackUrl" -ForegroundColor Green } } Write-Host "[OK] Git 仓库:$OpenClawGitRepoUrl" -ForegroundColor Green if ($NoGitUpdate) { Write-Host "[OK] Git 自动更新:已关闭" -ForegroundColor Green } else { Write-Host "[OK] Git 自动更新:已开启" -ForegroundColor Green } } if ($NoOnboard) { Write-Host "[OK] 首次引导设置:已跳过" -ForegroundColor Green } return } Remove-LegacySubmodule -RepoDir $RepoDir # 检查是否为升级安装 $isUpgrade = Check-ExistingOpenClaw # 第一步:检查 Node.js if (-not (Check-Node)) { Install-Node # 再次确认 Node.js 是否可用 if (-not (Check-Node)) { Write-Host "" Write-Host "错误:Node.js 安装后可能需要重新打开终端才能生效" -ForegroundColor Red Write-Host "请关闭当前终端,重新打开后再次运行此安装脚本。" -ForegroundColor Yellow exit 1 } } $finalGitDir = $null # 第二步:安装 OpenClaw if ($InstallMethod -eq "git") { $finalGitDir = $GitDir Install-OpenClawFromGit -RepoDir $GitDir -SkipUpdate:$NoGitUpdate } else { Install-OpenClaw } if (-not (Ensure-OpenClawOnPath)) { Write-Host "安装已完成,但当前还不能直接运行 OpenClaw 命令。" -ForegroundColor Yellow Write-Host "请打开一个新的终端窗口,然后运行:openclaw doctor" -ForegroundColor Cyan return } Refresh-GatewayServiceIfLoaded # 第三步:如果是升级安装或 Git 安装,则运行 doctor 做迁移 if ($isUpgrade -or $InstallMethod -eq "git") { Run-Doctor } $installedVersion = $null try { $installedVersion = (Invoke-OpenClawCommand --version 2>$null).Trim() } catch { $installedVersion = $null } if (-not $installedVersion) { try { $npmList = & (Get-NpmCommandPath) list -g --depth 0 --json 2>$null | ConvertFrom-Json if ($npmList -and $npmList.dependencies -and $npmList.dependencies.openclaw -and $npmList.dependencies.openclaw.version) { $installedVersion = $npmList.dependencies.openclaw.version } } catch { $installedVersion = $null } } Write-Host "" if ($installedVersion) { Write-Host "OpenClaw 已安装成功($installedVersion)!" -ForegroundColor Green } else { Write-Host "OpenClaw 已安装成功!" -ForegroundColor Green } Write-Host "本安装服务由 Influo AI 提供支持。" -ForegroundColor Gray Write-Host "" if ($isUpgrade) { $updateMessages = @( "升级完成,可以开始用了。", "已经更新到新版本。", "更新完成,功能已准备就绪。", "新版本安装好了,继续使用吧。", "已经替你更新好了。", "版本已升级完成。", "更新成功,现在是最新安装结果。", "新版已就位,可以继续操作。", "升级完成,运行环境已刷新。", "更新处理完成。" ) Write-Host (Get-Random -InputObject $updateMessages) -ForegroundColor Gray Write-Host "" } else { $completionMessages = @( "安装完成,可以开始使用了。", "已经准备就绪。", "OpenClaw 已成功安装。", "安装顺利完成。", "现在可以继续下一步设置了。", "环境已经准备好了。", "安装结束,接下来可以开始配置。", "所有必要内容都已安装完成。", "已经安装好,随时可以使用。", "处理完成。" ) Write-Host (Get-Random -InputObject $completionMessages) -ForegroundColor Gray Write-Host "" } if ($InstallMethod -eq "git") { Write-Host "源码目录:$finalGitDir" -ForegroundColor Cyan Write-Host "启动命令位置:$env:USERPROFILE\\.local\\bin\\openclaw.cmd" -ForegroundColor Cyan Write-Host "" } if ($isUpgrade) { Write-Host "升级完成。你可以运行 " -NoNewline Write-Host "openclaw doctor" -ForegroundColor Cyan -NoNewline Write-Host " 检查是否还有需要处理的迁移。" } else { if ($NoOnboard) { Write-Host "已按要求跳过初始设置。之后可运行 " -NoNewline Write-Host "openclaw onboard" -ForegroundColor Cyan -NoNewline Write-Host " 再继续。" } else { Write-Host "正在开始初始设置..." -ForegroundColor Cyan Write-Host "" Invoke-OpenClawCommand onboard } } } Main