Install and update artemis-use with task diagnostics and plugin guidance. Preserve cross-device file receipts, review canonical reports, guard stage transitions and reduce small history summaries. Improve exploration lifecycle, request recovery and browser live connections.
487 lines
26 KiB
PowerShell
487 lines
26 KiB
PowerShell
# Copyright 2026 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[int]$Port = 8000,
|
|
[switch]$NoOpen = $false,
|
|
[switch]$SetupOnly = $false,
|
|
[switch]$PrepareOnly = $false,
|
|
[switch]$SkipUpdate = $false,
|
|
[switch]$SkipMcpSync = $false,
|
|
[switch]$Help = $false,
|
|
[ValidateSet('Official', 'China')][string]$Mirror
|
|
)
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
function Get-LaunchText {
|
|
param([string]$English, [string]$Chinese)
|
|
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN') { return $Chinese }
|
|
return $English
|
|
}
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
$RootDir = Split-Path -Parent $ScriptDir
|
|
$ProjectVersion = 'unknown'
|
|
try {
|
|
$inProjectSection = $false
|
|
foreach ($line in Get-Content -LiteralPath (Join-Path $RootDir 'pyproject.toml') -Encoding UTF8 -ErrorAction Stop) {
|
|
if ($line -match '^\[project\]\s*$') { $inProjectSection = $true; continue }
|
|
if ($inProjectSection -and $line -match '^\[') { break }
|
|
if ($inProjectSection -and $line -match '^version\s*=\s*"([^"]+)"') {
|
|
$ProjectVersion = $Matches[1]
|
|
break
|
|
}
|
|
}
|
|
} catch {}
|
|
|
|
# Keep UTF-8 Chinese text in this BOM-encoded script, never in CMD batch files.
|
|
# CMD can misparse multibyte batch lines when an archive/editor converts CRLF to LF.
|
|
if ($Help) {
|
|
Write-Host (Get-LaunchText 'Usage: start.bat [options]' '用法:start-cn.bat [选项]')
|
|
Write-Host (Get-LaunchText "Version: v$ProjectVersion" "版本:v$ProjectVersion")
|
|
Write-Host ''
|
|
Write-Host (Get-LaunchText ' -SetupOnly Prepare the environment without launching the UI' ' -SetupOnly 安装或检查环境,不启动网页')
|
|
Write-Host (Get-LaunchText ' -Port 8001 Use the specified port (default: 8000)' ' -Port 8001 使用指定端口,默认 8000')
|
|
Write-Host (Get-LaunchText ' -NoOpen Do not open the browser automatically' ' -NoOpen 启动服务后不自动打开浏览器')
|
|
Write-Host (Get-LaunchText ' -SkipUpdate Skip the source update notification' ' -SkipUpdate 跳过源码更新提示(启动不会拉取源码)')
|
|
Write-Host (Get-LaunchText ' -SkipMcpSync Skip Codex MCP installation and updates' ' -SkipMcpSync 仅启动网页,跳过 Codex MCP 安装和更新')
|
|
Write-Host (Get-LaunchText ' -Mirror China Use China download mirrors' ' -Mirror China 使用国内下载镜像')
|
|
Write-Host (Get-LaunchText ' -Help Show this help' ' -Help 显示本说明')
|
|
Write-Host ''
|
|
Write-Host (Get-LaunchText 'Download sources: config\downloads.json' '国内镜像设置:config\downloads.json')
|
|
Write-Host (Get-LaunchText 'First launch requires Internet access. Upgrade with update.bat; startup only checks for updates.' '首次运行需联网;启动仅提示源码更新,升级请运行 update-cn.bat。')
|
|
exit 0
|
|
}
|
|
Write-Host '======================================================'
|
|
Write-Host (Get-LaunchText "Version: v$ProjectVersion" "版本:v$ProjectVersion")
|
|
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN' -and $Mirror -eq 'China') {
|
|
Write-Host ' ARTEMIS 中文启动 - 使用国内镜像'
|
|
Write-Host '======================================================'
|
|
Write-Host '正在检查已有环境、更新提示及依赖;首次启动可能需要下载组件。'
|
|
Write-Host '镜像地址和下载超时可在 config\downloads.json 中调整。'
|
|
} else {
|
|
Write-Host (Get-LaunchText ' Artemis Autonomous Mobile Agent UI' ' ARTEMIS 手机自动化测试')
|
|
Write-Host '======================================================'
|
|
}
|
|
Write-Host ''
|
|
|
|
# Ensure modern TLS protocols are enabled for downloads (GitHub releases, nodejs.org, Google repositories)
|
|
try {
|
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
|
|
} catch {}
|
|
|
|
Set-Location $RootDir
|
|
|
|
# Startup never mutates source; upgrades run from a staged, independent process.
|
|
. (Join-Path $ScriptDir 'installation_lock.ps1')
|
|
$installationLock = $null
|
|
if ($env:ARTEMIS_UPGRADE_CHILD -ne '1') {
|
|
try { $installationLock = Enter-ArtemisInstallationLock -ProjectRoot $RootDir }
|
|
catch { Write-Host (Get-LaunchText "[ERROR] $_" '[错误] 另一个环境准备或升级正在运行,请稍后重试。') -ForegroundColor Red; exit 1 }
|
|
}
|
|
$existingPython = Join-Path $RootDir '.venv\Scripts\python.exe'
|
|
if ((-not $SkipUpdate) -and (Test-Path -LiteralPath $existingPython)) {
|
|
& $existingPython (Join-Path $ScriptDir 'update_installation.py') source-check
|
|
# Network/authentication failures are warnings, never a reason to prevent startup.
|
|
}
|
|
. (Join-Path $ScriptDir 'windows_tools.ps1')
|
|
. (Join-Path $ScriptDir 'download_config.ps1')
|
|
try {
|
|
Initialize-ArtemisDownloads -ProjectRoot $RootDir -Mode $Mirror
|
|
} catch {
|
|
Write-Host (Get-LaunchText " [ERROR] Invalid download settings: $_" " [错误] 下载配置无效:$_") -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "======================================================" -ForegroundColor Cyan
|
|
Write-Host (Get-LaunchText " Artemis Autonomous Mobile Agent UI " " ARTEMIS 手机自动化测试平台") -ForegroundColor Cyan
|
|
Write-Host "======================================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
function Start-LocalAdbServer {
|
|
if (Test-CommandExists "adb") {
|
|
$adbErrorPreference = $ErrorActionPreference
|
|
try {
|
|
# Windows PowerShell 5.1 wraps native stderr in ErrorRecord objects.
|
|
# ADB prints normal daemon-start messages there, so inspect its exit code.
|
|
$ErrorActionPreference = "Continue"
|
|
# Explicit options ignore inherited remote-server environment settings.
|
|
$adbOutput = & adb -H localhost -P 5037 start-server 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host " [警告] 本机 ADB 服务启动失败(退出码 $LASTEXITCODE)。" -ForegroundColor Yellow
|
|
Write-Host ($adbOutput | Out-String).Trim() -ForegroundColor Yellow
|
|
Write-Host " [操作] 请检查 ADB 安装、端口占用或安全软件拦截;本机设备暂不可用。" -ForegroundColor Yellow
|
|
}
|
|
} catch {
|
|
Write-Host " [警告] 本机 ADB 服务启动异常:$_" -ForegroundColor Yellow
|
|
} finally {
|
|
$ErrorActionPreference = $adbErrorPreference
|
|
}
|
|
}
|
|
}
|
|
|
|
function Install-PortablePlatformTools {
|
|
$ptDir = "$env:LOCALAPPDATA\Programs\platform-tools"
|
|
if (Test-Path "$ptDir\adb.exe") {
|
|
$env:PATH = "$ptDir;$env:PATH"
|
|
return $true
|
|
}
|
|
Write-Host (Get-LaunchText " [INFO] Installing Android platform-tools (adb) in user space..." " [安装] 正在安装 Android 调试工具 adb(当前用户)...") -ForegroundColor Cyan
|
|
try {
|
|
$zipPath = "$env:TEMP\platform-tools-windows.zip"
|
|
if (Invoke-DownloadFile -Uri $ArtemisDownloads.adb_url -OutFile $zipPath) {
|
|
New-Item -ItemType Directory -Path "$env:LOCALAPPDATA\Programs" -Force | Out-Null
|
|
Expand-Archive -Path $zipPath -DestinationPath "$env:LOCALAPPDATA\Programs" -Force
|
|
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
|
if (Test-Path "$ptDir\adb.exe") {
|
|
$env:PATH = "$ptDir;$env:PATH"
|
|
Write-Host (Get-LaunchText " [OK] adb installed in user space." " [完成] adb 已安装。") -ForegroundColor Green
|
|
return $true
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Host (Get-LaunchText " [WARN] Failed to install portable platform-tools: $_" " [提示] adb 安装失败:$_") -ForegroundColor DarkYellow
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Install-PortableScrcpy {
|
|
$scrcpyDir = "$env:LOCALAPPDATA\Programs\scrcpy"
|
|
if (Test-Path "$scrcpyDir\scrcpy.exe") {
|
|
$env:PATH = "$scrcpyDir;$env:PATH"
|
|
return $true
|
|
}
|
|
# Check if nested from an earlier run and heal
|
|
$nested = Get-ChildItem -Path $scrcpyDir -Directory -Filter "scrcpy*" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($nested -and (Test-Path "$($nested.FullName)\scrcpy.exe")) {
|
|
Copy-Item -Path "$($nested.FullName)\*" -Destination $scrcpyDir -Recurse -Force
|
|
Remove-Item -Path $nested.FullName -Recurse -Force -ErrorAction SilentlyContinue
|
|
if (Test-Path "$scrcpyDir\scrcpy.exe") {
|
|
$env:PATH = "$scrcpyDir;$env:PATH"
|
|
return $true
|
|
}
|
|
}
|
|
|
|
Write-Host (Get-LaunchText " [INFO] Installing portable scrcpy in user space..." " [安装] 正在安装 scrcpy(当前用户)...") -ForegroundColor Cyan
|
|
if ($ArtemisDownloads.scrcpy_urls.Count -eq 0) {
|
|
Write-Host (Get-LaunchText ' [WARN] No scrcpy download source is configured. Set overrides.scrcpy_url.' ' [提示] 未配置可用的 scrcpy 下载源;请设置 overrides.scrcpy_url。') -ForegroundColor Yellow
|
|
return $false
|
|
}
|
|
$work = Join-Path ([IO.Path]::GetTempPath()) ('artemis-scrcpy-' + [Guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Path $work -ErrorAction Stop | Out-Null
|
|
try {
|
|
$zipPath = Join-Path $work 'scrcpy.zip'
|
|
if (Invoke-ArtemisVerifiedDownload -Urls $ArtemisDownloads.scrcpy_urls -OutFile $zipPath -Sha256 $ArtemisDownloads.scrcpy_sha256) {
|
|
$extractDir = Join-Path $work 'extracted'
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractDir)
|
|
$extractedFolder = Get-ChildItem -Path $extractDir -Directory | Select-Object -First 1
|
|
$contentDir = if (Test-Path -LiteralPath (Join-Path $extractDir 'scrcpy.exe')) { $extractDir }
|
|
elseif ($extractedFolder) { $extractedFolder.FullName }
|
|
else { throw 'scrcpy ZIP contains no application directory.' }
|
|
if (-not (Test-Path -LiteralPath (Join-Path $contentDir 'scrcpy.exe'))) { throw 'scrcpy ZIP contains no scrcpy.exe.' }
|
|
New-Item -ItemType Directory -Path $scrcpyDir -Force -ErrorAction Stop | Out-Null
|
|
Get-ChildItem -LiteralPath $contentDir | Copy-Item -Destination $scrcpyDir -Recurse -Force -ErrorAction Stop
|
|
$env:PATH = "$scrcpyDir;$env:PATH"
|
|
Write-Host (Get-LaunchText " [OK] Portable scrcpy installed in user space." " [完成] scrcpy 已安装。") -ForegroundColor Green
|
|
return $true
|
|
}
|
|
} catch {
|
|
Write-Host (Get-LaunchText " [WARN] Failed to install portable scrcpy: $_" " [提示] scrcpy 安装失败:$_") -ForegroundColor DarkYellow
|
|
} finally {
|
|
$fullWork = [IO.Path]::GetFullPath($work)
|
|
$tempPrefix = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
|
|
if ($fullWork.StartsWith($tempPrefix, [StringComparison]::OrdinalIgnoreCase) -and (Split-Path $fullWork -Leaf) -like 'artemis-scrcpy-*') {
|
|
Remove-Item -LiteralPath $fullWork -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Test-NodeCompatible {
|
|
if (-not (Test-CommandExists "node") -or (-not (Test-CommandExists "npm") -and -not (Test-CommandExists "npm.cmd"))) { return $false }
|
|
try {
|
|
$verRaw = (& node -v 2>$null)
|
|
if (-not $verRaw) { return $false }
|
|
$verStr = $verRaw.ToString().Trim().TrimStart('v')
|
|
$parts = $verStr.Split('.')
|
|
if ($parts.Count -ge 2) {
|
|
$major = [int]$parts[0]
|
|
$minor = [int]$parts[1]
|
|
if ($major -ge 26) { return $true }
|
|
if ($major -eq 24 -and $minor -ge 15) { return $true }
|
|
if ($major -eq 22 -and $minor -ge 22) { return $true }
|
|
}
|
|
} catch {
|
|
return $false
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Install-PortableNode {
|
|
$nodeDir = "$env:LOCALAPPDATA\Programs\node"
|
|
if (Test-Path "$nodeDir\node.exe") {
|
|
$env:PATH = "$nodeDir;$env:PATH"
|
|
if (Test-NodeCompatible) {
|
|
return $true
|
|
}
|
|
}
|
|
# Check if nested from an earlier run and heal
|
|
$nested = Get-ChildItem -Path $nodeDir -Directory -Filter "node-*" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($nested -and (Test-Path "$($nested.FullName)\node.exe")) {
|
|
Copy-Item -Path "$($nested.FullName)\*" -Destination $nodeDir -Recurse -Force
|
|
Remove-Item -Path $nested.FullName -Recurse -Force -ErrorAction SilentlyContinue
|
|
if (Test-Path "$nodeDir\node.exe") {
|
|
$env:PATH = "$nodeDir;$env:PATH"
|
|
if (Test-NodeCompatible) {
|
|
return $true
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host (Get-LaunchText " [INFO] Installing portable Node.js LTS (v22.23.2) in user space..." " [安装] 正在安装 Node.js LTS v22.23.2(当前用户)...") -ForegroundColor Cyan
|
|
try {
|
|
$nodeVer = "v22.23.2"
|
|
$arch = if ([System.Environment]::Is64BitOperatingSystem) {
|
|
if ($env:PROCESSOR_ARCHITECTURE -match "ARM64") { "arm64" } else { "x64" }
|
|
} else { "x64" }
|
|
$zipPath = "$env:TEMP\node-$nodeVer-win-$arch.zip"
|
|
$nodeBase = $ArtemisDownloads.node_mirror.TrimEnd('/')
|
|
if (Invoke-DownloadFile -Uri "$nodeBase/$nodeVer/node-$nodeVer-win-$arch.zip" -OutFile $zipPath) {
|
|
$extractDir = "$env:TEMP\node_extract"
|
|
if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue }
|
|
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
|
|
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
|
|
$extractedFolder = Get-ChildItem -Path $extractDir -Directory | Select-Object -First 1
|
|
if ($extractedFolder) {
|
|
if (-not (Test-Path $nodeDir)) {
|
|
New-Item -ItemType Directory -Path $nodeDir -Force | Out-Null
|
|
}
|
|
Copy-Item -Path "$($extractedFolder.FullName)\*" -Destination $nodeDir -Recurse -Force
|
|
}
|
|
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
|
Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
if (Test-Path "$nodeDir\node.exe") {
|
|
$env:PATH = "$nodeDir;$env:PATH"
|
|
Write-Host (Get-LaunchText " [OK] Portable Node.js $nodeVer installed in user space." " [完成] Node.js $nodeVer 已安装。") -ForegroundColor Green
|
|
return $true
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Host (Get-LaunchText " [WARN] Failed to install portable Node.js: $_" " [提示] Node.js 安装失败:$_") -ForegroundColor DarkYellow
|
|
}
|
|
return $false
|
|
}
|
|
|
|
# 1. Refresh PATH with standard toolchain paths
|
|
Update-EnvironmentPath
|
|
|
|
# 2. Prepare the project Python environment before optional device/UI setup.
|
|
. (Join-Path $ScriptDir 'bootstrap_python.ps1')
|
|
try {
|
|
$ArtemisPython = Initialize-ArtemisPython -ProjectRoot $RootDir
|
|
} catch {
|
|
Write-Host " [ERROR] $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath (Join-Path $RootDir '.env'))) {
|
|
if (Test-Path -LiteralPath (Join-Path $RootDir '.env.example')) {
|
|
Copy-Item -LiteralPath (Join-Path $RootDir '.env.example') -Destination (Join-Path $RootDir '.env') -ErrorAction Stop
|
|
Write-Host (Get-LaunchText ' [OK] Created .env from .env.example. Configure your model in the web UI.' ' [完成] 已按模板创建 .env;请在网页中配置模型。') -ForegroundColor Green
|
|
}
|
|
}
|
|
if (-not $SkipMcpSync) {
|
|
Write-Host (Get-LaunchText ' [CHECK] Installing/updating Codex MCP and artemis-use Skill...' ' [检查] 正在安装或更新 Codex MCP 和 artemis-use Skill...') -ForegroundColor Cyan
|
|
& $ArtemisPython (Join-Path $ScriptDir 'update_installation.py') mcp
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host (Get-LaunchText ' [WARN] Codex integration sync failed; existing configuration is preserved. Continuing web startup.' ' [提示] Codex 集成同步失败;保留原配置,继续启动网页。') -ForegroundColor Red
|
|
}
|
|
} else {
|
|
$env:ARTEMIS_SKIP_CODEX_SYNC = '1'
|
|
Write-Host (Get-LaunchText ' [SKIP] MCP synchronization was skipped.' ' [跳过] 已跳过 MCP 同步。') -ForegroundColor Yellow
|
|
}
|
|
if ($SetupOnly -and -not $PrepareOnly) {
|
|
Write-Host (Get-LaunchText ' [OK] Setup complete. Run start.bat to launch Artemis.' ' [完成] 环境已准备好。运行 start-cn.bat 启动 ARTEMIS。') -ForegroundColor Green
|
|
exit 0
|
|
}
|
|
|
|
# 3. Check and try installing missing system toolchains (ADB, FFmpeg, scrcpy)
|
|
$missingCore = @()
|
|
if (-not (Test-CommandExists "adb")) { $missingCore += "adb" }
|
|
# imageio-ffmpeg ships a working executable in its wheel, including mirror installs.
|
|
if (-not (Test-CommandExists "ffmpeg")) {
|
|
& $ArtemisPython -c 'import imageio_ffmpeg; imageio_ffmpeg.get_ffmpeg_exe()' 2>$null | Out-Null
|
|
if ($LASTEXITCODE -ne 0) { $missingCore += "ffmpeg" }
|
|
}
|
|
if (-not (Test-CommandExists "scrcpy")) { $missingCore += "scrcpy" }
|
|
|
|
if ($missingCore.Count -gt 0) {
|
|
$useWinGet = $false
|
|
if ($ArtemisDownloads.mode -ne 'China' -and (Test-CommandExists "winget")) {
|
|
if ([Console]::IsInputRedirected -eq $false) {
|
|
Write-Host (Get-LaunchText " [INFO] Missing components detected: $($missingCore -join ', ')." " [检查] 缺少组件:$($missingCore -join ', ')。") -ForegroundColor Cyan
|
|
$ans = Read-Host (Get-LaunchText " Install system packages via WinGet? [Y/n]" " 通过 WinGet 安装?[Y/n]")
|
|
if ($ans -eq "" -or $ans -match "^[Yy]") {
|
|
$useWinGet = $true
|
|
}
|
|
} else {
|
|
$useWinGet = $true
|
|
}
|
|
}
|
|
|
|
if ($useWinGet) {
|
|
Write-Host (Get-LaunchText " [INFO] Auto-installing missing components ($($missingCore -join ', '))..." " [安装] 正在安装缺少的组件:$($missingCore -join ', ')...") -ForegroundColor DarkYellow
|
|
if ($missingCore -contains "adb") {
|
|
winget install --id Google.PlatformTools -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
|
}
|
|
if ($missingCore -contains "ffmpeg") {
|
|
winget install --id Gyan.FFmpeg -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
|
}
|
|
if ($missingCore -contains "scrcpy") {
|
|
winget install --id Genymobile.scrcpy -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
|
}
|
|
Update-EnvironmentPath
|
|
}
|
|
|
|
# Zero-admin user-space fallback for adb and scrcpy if still missing
|
|
if (-not (Test-CommandExists "adb")) {
|
|
Install-PortablePlatformTools
|
|
}
|
|
if (-not (Test-CommandExists "scrcpy")) {
|
|
if (-not (Install-PortableScrcpy)) {
|
|
Write-Host (Get-LaunchText ' [WARN] All scrcpy sources failed. Check the HTTP/timeout/checksum error above, or configure overrides.scrcpy_url and scrcpy_fallback_urls in config/downloads.json.' ' [提示] scrcpy 下载源均未成功;请查看上方 HTTP、超时或校验错误,或在 config/downloads.json 中设置 overrides.scrcpy_url 与备用地址 scrcpy_fallback_urls。') -ForegroundColor Yellow
|
|
}
|
|
}
|
|
}
|
|
|
|
# Ensure local ADB daemon is warm & listening so probes do not hit 'Connection refused'
|
|
if (-not $PrepareOnly) { Start-LocalAdbServer }
|
|
|
|
# 6. Check and build Showcase UI if not already compiled
|
|
$ShowcaseIndex = "$RootDir\apps\showcase_ui\dist\frontend\browser\index.html"
|
|
$ShowcaseIndexAlt1 = "$RootDir\apps\showcase_ui\dist\browser\index.html"
|
|
$ShowcaseIndexAlt2 = "$RootDir\apps\showcase_ui\dist\index.html"
|
|
& $ArtemisPython (Join-Path $ScriptDir 'update_installation.py') frontend-check
|
|
$frontendChanged = $LASTEXITCODE -eq 10
|
|
if ($LASTEXITCODE -notin @(0, 10)) { Write-Host (Get-LaunchText ' [ERROR] Could not check frontend sources.' ' [错误] 无法检查前端源码。') -ForegroundColor Red; exit 1 }
|
|
if ($frontendChanged -or ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Path $ShowcaseIndexAlt1)) -and (-not (Test-Path $ShowcaseIndexAlt2)))) {
|
|
if (-not (Test-NodeCompatible)) {
|
|
if (Test-CommandExists "node") {
|
|
$curVer = (& node -v 2>$null)
|
|
Write-Host (Get-LaunchText " [INFO] Detected Node.js $curVer, but Angular CLI requires Node.js >= v22.22.0." " [检查] 检测到 Node.js $curVer;前端需要 v22.22.0 或更新版本。") -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host (Get-LaunchText " [INFO] Node.js/npm not found (required for Showcase UI)." " [检查] 未找到 Node.js/npm,网页构建需要安装。") -ForegroundColor Cyan
|
|
}
|
|
|
|
# 1. Try nvm if available on Windows
|
|
if ($ArtemisDownloads.mode -ne 'China' -and (Test-CommandExists "nvm")) {
|
|
Write-Host (Get-LaunchText " [INFO] Installing Node.js 22 LTS via nvm..." " [安装] 正在通过 nvm 安装 Node.js 22 LTS...") -ForegroundColor Cyan
|
|
& nvm install 22.23.2 | Out-Null
|
|
& nvm use 22.23.2 | Out-Null
|
|
Update-EnvironmentPath
|
|
}
|
|
|
|
# 2. Try WinGet
|
|
if (-not (Test-NodeCompatible)) {
|
|
$useWinGetNode = $false
|
|
if ($ArtemisDownloads.mode -ne 'China' -and (Test-CommandExists "winget")) {
|
|
if ([Console]::IsInputRedirected -eq $false) {
|
|
Write-Host (Get-LaunchText " [INFO] Node.js >= v22.22.0 required for Showcase UI." " [检查] 网页需要 Node.js v22.22.0 或更新版本。") -ForegroundColor Cyan
|
|
$ans = Read-Host (Get-LaunchText " Install/Upgrade Node.js via WinGet (may require Administrator)? [Y/n]" " 通过 WinGet 安装/升级 Node.js(可能需要管理员权限)?[Y/n]")
|
|
if ($ans -eq "" -or $ans -match "^[Yy]") {
|
|
$useWinGetNode = $true
|
|
}
|
|
} else {
|
|
$useWinGetNode = $true
|
|
}
|
|
}
|
|
|
|
if ($useWinGetNode) {
|
|
Write-Host (Get-LaunchText " [INFO] Installing/Upgrading Node.js LTS via WinGet..." " [安装] 正在通过 WinGet 安装/升级 Node.js LTS...") -ForegroundColor Yellow
|
|
winget install --id OpenJS.NodeJS.LTS -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
|
Update-EnvironmentPath
|
|
}
|
|
}
|
|
|
|
# 3. Zero-admin user-space fallback for Node.js if still not compatible
|
|
if (-not (Test-NodeCompatible)) {
|
|
Install-PortableNode
|
|
}
|
|
}
|
|
|
|
if (Test-NodeCompatible) {
|
|
$nodeVer = (& node -v 2>$null)
|
|
Write-Host (Get-LaunchText " [OK] Node.js $nodeVer is ready." " [完成] Node.js $nodeVer 已就绪。") -ForegroundColor Green
|
|
Write-Host (Get-LaunchText " [INFO] Showcase UI sources/dependencies changed or build missing. Compiling..." " [构建] 前端源码/依赖有变化或缺少构建结果,准备重新构建...") -ForegroundColor Yellow
|
|
Push-Location "$RootDir\apps\showcase_ui"
|
|
try {
|
|
$npmExec = if (Test-CommandExists "npm.cmd") { "npm.cmd" } else { "npm" }
|
|
Write-Host (Get-LaunchText " [INFO] Installing frontend npm dependencies..." " [安装] 正在安装前端 npm 依赖...") -ForegroundColor Cyan
|
|
& $npmExec ci --no-audit --no-fund --loglevel=error
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "npm ci failed (exit $LASTEXITCODE). Check the network and retry."
|
|
}
|
|
Write-Host (Get-LaunchText " [INFO] Building Angular frontend application..." " [构建] 正在构建 Angular 网页...") -ForegroundColor Cyan
|
|
& $npmExec run build
|
|
if ($LASTEXITCODE -ne 0) { throw "Frontend build failed (exit $LASTEXITCODE)." }
|
|
if (Test-Path $ShowcaseIndex) {
|
|
Write-Host (Get-LaunchText " [OK] Showcase UI compiled successfully." " [完成] 网页构建成功。") -ForegroundColor Green
|
|
} elseif ((Test-Path $ShowcaseIndexAlt1) -or (Test-Path $ShowcaseIndexAlt2)) {
|
|
Write-Host (Get-LaunchText " [OK] Showcase UI compiled successfully." " [完成] 网页构建成功。") -ForegroundColor Green
|
|
} else {
|
|
throw 'Frontend build did not produce index.html.'
|
|
}
|
|
& $ArtemisPython (Join-Path $ScriptDir 'update_installation.py') frontend-mark
|
|
if ($LASTEXITCODE -ne 0) { throw 'Could not save frontend build fingerprint.' }
|
|
} catch {
|
|
Write-Host (Get-LaunchText " [ERROR] Failed to build Showcase UI: $_" " [错误] 网页构建失败:$_") -ForegroundColor Red
|
|
exit 1
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
} else {
|
|
Write-Host (Get-LaunchText " [ERROR] A compatible Node.js/npm could not be installed. Install Node.js LTS and retry." " [错误] 无法安装兼容的 Node.js/npm。请安装 Node.js LTS 后重试。") -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# MCP clients were refreshed after Python dependency sync above.
|
|
|
|
Exit-ArtemisInstallationLock -Mutex $installationLock
|
|
$installationLock = $null
|
|
if ($PrepareOnly) {
|
|
Write-Host (Get-LaunchText '[OK] Python, frontend and Codex MCP preparation complete.' '[完成] Python、网页构建和 Codex MCP 已准备好。') -ForegroundColor Green
|
|
exit 0
|
|
}
|
|
# 8. Launch unified Showcase UI & open browser
|
|
# Ensure local ADB daemon is warm & listening
|
|
if (-not $PrepareOnly) { Start-LocalAdbServer }
|
|
|
|
# Launch via `python -m artemis` (not the `artemis.exe` console-script shim) so the
|
|
# long-running server never locks .venv\Scripts\artemis.exe against `uv sync` reinstalls.
|
|
Write-Host (Get-LaunchText " [INFO] Launching Artemis Showcase UI & Admin Console..." " [启动] 正在启动 ARTEMIS 网页与管理服务...") -ForegroundColor Green
|
|
if ($NoOpen) {
|
|
& $ArtemisPython -m artemis ui --port $Port --no-open
|
|
} else {
|
|
& $ArtemisPython -m artemis ui --port $Port --open
|
|
}
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
exit $LASTEXITCODE
|
|
}
|