Files
Artemis-Eapil/scripts/start.ps1
T
chenmingxuan 2ddec53954
CI / Python 3.12 / ubuntu-latest (push) Canceled after 0s
CI / Python 3.12 / windows-latest (push) Canceled after 0s
CI / Frontend tests and production build (push) Canceled after 0s
CI / Build and smoke-test Python packages (push) Canceled after 0s
feat: add Chinese UI, Responses models, and mirrored Windows setup
Localize the web console and persist custom model and capability settings.
Use OpenAI Responses exclusively and move runtime limits into environment configuration.
Bootstrap uv and the virtual environment on Windows, support optional China mirrors,
and package source distributions without environments, build output, or credentials.

Update regression tests and repository quality checks for the new behavior.
2026-09-22 19:16:17 +08:00

507 lines
22 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,
[ValidateSet('Official', 'China')][string]$Mirror
)
$ErrorActionPreference = "Continue"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# 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 {}
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$RootDir = Split-Path -Parent $ScriptDir
Set-Location $RootDir
. (Join-Path $ScriptDir 'download_config.ps1')
try {
Initialize-ArtemisDownloads -ProjectRoot $RootDir -Mode $Mirror
} catch {
Write-Host " [ERROR] Invalid download settings: $_" -ForegroundColor Red
exit 1
}
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " Artemis Autonomous Mobile Agent UI " -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
function Update-EnvironmentPath {
$standardDirs = @(
"$env:LOCALAPPDATA\Microsoft\WinGet\Links",
"$env:LOCALAPPDATA\Programs\node",
"$env:USERPROFILE\.local\share\node",
"$env:LOCALAPPDATA\Programs\scrcpy",
"$env:USERPROFILE\.local\share\scrcpy",
"$env:LOCALAPPDATA\Programs\platform-tools",
"$env:USERPROFILE\.local\share\platform-tools",
"$env:LOCALAPPDATA\Programs\uv",
"$env:LOCALAPPDATA\uv",
"$env:APPDATA\uv",
"$env:USERPROFILE\.cargo\bin",
"$env:USERPROFILE\.local\bin",
"$env:USERPROFILE\scoop\shims",
"C:\ProgramData\chocolatey\bin",
"$env:LOCALAPPDATA\Android\Sdk\platform-tools",
"$env:LOCALAPPDATA\Android\android-sdk\platform-tools",
"$env:ProgramFiles\Android\platform-tools",
"${env:ProgramFiles(x86)}\Android\android-sdk\platform-tools",
"$env:ProgramFiles\nodejs",
"${env:ProgramFiles(x86)}\nodejs",
"$env:APPDATA\npm"
)
if ($env:CARGO_HOME) { $standardDirs = @("$env:CARGO_HOME\bin") + $standardDirs }
if ($env:ANDROID_HOME) { $standardDirs += "$env:ANDROID_HOME\platform-tools" }
if ($env:ANDROID_SDK_ROOT) { $standardDirs += "$env:ANDROID_SDK_ROOT\platform-tools" }
if ($env:NVM_SYMLINK) { $standardDirs = @($env:NVM_SYMLINK) + $standardDirs }
if ($env:NVM_HOME) { $standardDirs = @($env:NVM_HOME) + $standardDirs }
$regPath = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine")
$currentPaths = ($env:PATH -split ";") + ($regPath -split ";") + $standardDirs | Where-Object { [string]::IsNullOrWhiteSpace($_) -eq $false -and (Test-Path $_) } | Select-Object -Unique
$env:PATH = $currentPaths -join ";"
# Ensure user-space portable tools take absolute priority over system-level outdated versions
$portableNode = "$env:LOCALAPPDATA\Programs\node"
if (Test-Path "$portableNode\node.exe") {
$env:PATH = "$portableNode;$env:PATH"
}
$portableScrcpy = "$env:LOCALAPPDATA\Programs\scrcpy"
if (Test-Path "$portableScrcpy\scrcpy.exe") {
$env:PATH = "$portableScrcpy;$env:PATH"
}
$portablePt = "$env:LOCALAPPDATA\Programs\platform-tools"
if (Test-Path "$portablePt\adb.exe") {
$env:PATH = "$portablePt;$env:PATH"
}
}
function Start-LocalAdbServer {
if (Test-CommandExists "adb") {
try {
$origSocket = $env:ADB_SERVER_SOCKET
Remove-Item Env:\ADB_SERVER_SOCKET -ErrorAction SilentlyContinue
& adb start-server 2>$null | Out-Null
if ($origSocket) { $env:ADB_SERVER_SOCKET = $origSocket }
} catch {}
}
}
function Test-CommandExists {
param([string]$Command)
$res = Get-Command $Command -ErrorAction SilentlyContinue
return ($null -ne $res)
}
function Invoke-DownloadFile {
param(
[Parameter(Mandatory=$true)][string]$Uri,
[Parameter(Mandatory=$true)][string]$OutFile,
[int]$TimeoutSec = $ArtemisDownloads.timeout_seconds
)
if (Test-Path $OutFile) {
Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
}
# 1. Try Windows built-in curl.exe first (available in Win 10 1803+ and Win 11)
if (Test-CommandExists "curl.exe") {
try {
& curl.exe -f -sSL --connect-timeout 5 --max-time $TimeoutSec "$Uri" -o "$OutFile" 2>$null
if ($LASTEXITCODE -eq 0 -and (Test-Path $OutFile) -and ((Get-Item $OutFile).Length -gt 0)) {
return $true
}
} catch {}
if (Test-Path $OutFile) {
Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
}
}
# 2. Fallback to Invoke-WebRequest (suppressing progress bar to eliminate PowerShell buffer hangs)
try {
$prevProgress = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -TimeoutSec $TimeoutSec -UseBasicParsing -ErrorAction Stop
$ProgressPreference = $prevProgress
if ((Test-Path $OutFile) -and ((Get-Item $OutFile).Length -gt 0)) {
return $true
}
} catch {
$ProgressPreference = $prevProgress
}
if (Test-Path $OutFile) {
Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
}
return $false
}
function Install-PortablePlatformTools {
$ptDir = "$env:LOCALAPPDATA\Programs\platform-tools"
if (Test-Path "$ptDir\adb.exe") {
$env:PATH = "$ptDir;$env:PATH"
return $true
}
Write-Host " [INFO] Installing Android platform-tools (adb) in user space..." -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 " [OK] adb installed in user space." -ForegroundColor Green
return $true
}
}
} catch {
Write-Host " [WARN] Failed to install portable platform-tools: $_" -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 " [INFO] Installing portable scrcpy in user space..." -ForegroundColor Cyan
if ($ArtemisDownloads.mode -eq 'China' -and $ArtemisDownloads.scrcpy_url.StartsWith('https://github.com/')) {
Write-Host ' [INFO] scrcpy uses GitHub. If unreachable, set overrides.scrcpy_url in config/downloads.json.' -ForegroundColor Yellow
}
try {
$zipPath = "$env:TEMP\scrcpy-win64.zip"
if (Invoke-DownloadFile -Uri $ArtemisDownloads.scrcpy_url -OutFile $zipPath) {
$extractDir = "$env:TEMP\scrcpy_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 $scrcpyDir)) {
New-Item -ItemType Directory -Path $scrcpyDir -Force | Out-Null
}
Copy-Item -Path "$($extractedFolder.FullName)\*" -Destination $scrcpyDir -Recurse -Force
}
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
if (Test-Path "$scrcpyDir\scrcpy.exe") {
$env:PATH = "$scrcpyDir;$env:PATH"
Write-Host " [OK] Portable scrcpy installed in user space." -ForegroundColor Green
return $true
}
}
} catch {
Write-Host " [WARN] Failed to install portable scrcpy: $_" -ForegroundColor DarkYellow
}
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 " [INFO] Installing portable Node.js LTS (v22.23.2) in user space..." -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 " [OK] Portable Node.js $nodeVer installed in user space." -ForegroundColor Green
return $true
}
}
} catch {
Write-Host " [WARN] Failed to install portable 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 ' [OK] Created .env from .env.example. Configure your model in the web UI.' -ForegroundColor Green
}
}
if ($SetupOnly) {
Write-Host ' [OK] Setup complete. Run start.bat to launch 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 " [INFO] Missing components detected: $($missingCore -join ', ')." -ForegroundColor Cyan
$ans = Read-Host " Install system packages via WinGet? [Y/n]"
if ($ans -eq "" -or $ans -match "^[Yy]") {
$useWinGet = $true
}
} else {
$useWinGet = $true
}
}
if ($useWinGet) {
Write-Host " [INFO] Auto-installing missing components ($($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 ' [WARN] scrcpy download failed. Set overrides.scrcpy_url in config/downloads.json to a reachable release ZIP, then retry.' -ForegroundColor Yellow
}
}
}
# Ensure local ADB daemon is warm & listening so probes do not hit 'Connection refused'
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"
if ((-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 " [INFO] Detected Node.js $curVer, but Angular CLI requires Node.js >= v22.22.0." -ForegroundColor Yellow
} else {
Write-Host " [INFO] Node.js/npm not found (required for Showcase UI)." -ForegroundColor Cyan
}
# 1. Try nvm if available on Windows
if ($ArtemisDownloads.mode -ne 'China' -and (Test-CommandExists "nvm")) {
Write-Host " [INFO] Installing Node.js 22 LTS via nvm..." -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 " [INFO] Node.js >= v22.22.0 required for Showcase UI." -ForegroundColor Cyan
$ans = Read-Host " Install/Upgrade Node.js via WinGet (may require Administrator)? [Y/n]"
if ($ans -eq "" -or $ans -match "^[Yy]") {
$useWinGetNode = $true
}
} else {
$useWinGetNode = $true
}
}
if ($useWinGetNode) {
Write-Host " [INFO] Installing/Upgrading Node.js LTS via WinGet..." -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 " [OK] Node.js $nodeVer is ready." -ForegroundColor Green
Write-Host " [INFO] Showcase UI build not found. Compiling Angular Showcase UI..." -ForegroundColor Yellow
Push-Location "$RootDir\apps\showcase_ui"
try {
$npmExec = if (Test-CommandExists "npm.cmd") { "npm.cmd" } else { "npm" }
Write-Host " [INFO] Installing frontend npm dependencies..." -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 " [INFO] Building Angular frontend application..." -ForegroundColor Cyan
& $npmExec run build
if ($LASTEXITCODE -ne 0) { throw "Frontend build failed (exit $LASTEXITCODE)." }
if (Test-Path $ShowcaseIndex) {
Write-Host " [OK] Showcase UI compiled successfully." -ForegroundColor Green
} elseif (Test-Path $ShowcaseIndexAlt1) {
Write-Host " [OK] Showcase UI compiled successfully." -ForegroundColor Green
} else {
throw 'Frontend build did not produce index.html.'
}
} catch {
Write-Host " [ERROR] Failed to build Showcase UI: $_" -ForegroundColor Red
exit 1
} finally {
Pop-Location
}
} else {
Write-Host " [ERROR] A compatible Node.js/npm could not be installed. Install Node.js LTS and retry." -ForegroundColor Red
exit 1
}
}
# 7. Optionally configure MCP for detected AI IDEs
Write-Host ""
Write-Host " [INFO] Would you like to configure ARTEMIS MCP & testing rules for your AI IDEs?" -ForegroundColor Cyan
Write-Host " (Supported: Antigravity, Cursor, Claude Code/Desktop, Codex, OpenClaw, Windsurf)"
$installMcp = "Y"
if ([Console]::IsInputRedirected -eq $false) {
$response = Read-Host " Install MCP configuration & rules now? [Y/n]"
if ($response -ne "") {
$installMcp = $response
}
} else {
$installMcp = "N"
}
if ($installMcp -match "^[Yy]") {
Write-Host " Installing MCP server configuration & testing rules..." -ForegroundColor Cyan
& $ArtemisPython -m artemis mcp --install all
if ($LASTEXITCODE -eq 0) {
Write-Host " [OK] MCP configuration and rules installed successfully." -ForegroundColor Green
Write-Host " [TIP] You can update or re-install anytime with: uv run artemis mcp --install all" -ForegroundColor Cyan
} else {
Write-Host " [WARN] MCP installation failed. Review the error above and retry with: uv run artemis mcp --install all" -ForegroundColor Yellow
}
} else {
Write-Host " [SKIP] You can install MCP anytime later with: uv run artemis mcp --install all" -ForegroundColor Yellow
}
Write-Host ""
# 8. Launch unified Showcase UI & open browser
# Ensure local ADB daemon is warm & listening
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 " [INFO] Launching Artemis Showcase UI & Admin Console..." -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
}