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.
104 lines
6.6 KiB
PowerShell
104 lines
6.6 KiB
PowerShell
# Download settings are process-local; never edit global pip/npm/uv settings.
|
|
# Keep this file ASCII for Windows PowerShell 5.1.
|
|
|
|
function Initialize-ArtemisDownloads {
|
|
param([string]$ProjectRoot, [string]$Mode)
|
|
$path = Join-Path $ProjectRoot 'config\downloads.json'
|
|
$config = Get-Content -LiteralPath $path -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
if (-not $Mode) { $Mode = $config.mode }
|
|
if ($Mode -notin @('Official', 'China')) { throw 'Download mode must be Official or China.' }
|
|
$timeout = 0
|
|
if (-not [int]::TryParse([string]$config.timeout_seconds, [ref]$timeout) -or $timeout -lt 10 -or $timeout -gt 3600) {
|
|
throw 'config/downloads.json: timeout_seconds must be between 10 and 3600.'
|
|
}
|
|
$values = @{
|
|
python_index = ''; python_download_mirror = ''; npm_registry = ''
|
|
node_mirror = 'https://nodejs.org/dist'
|
|
adb_url = 'https://dl.google.com/android/repository/platform-tools-latest-windows.zip'
|
|
scrcpy_url = 'https://github.com/Genymobile/scrcpy/releases/download/v4.1/scrcpy-win64-v4.1.zip'
|
|
uv_installer_url = 'https://astral.sh/uv/install.ps1'
|
|
}
|
|
foreach ($name in @($values.Keys)) {
|
|
if ($Mode -eq 'China' -and $config.china.$name) { $values[$name] = [string]$config.china.$name }
|
|
if ($config.overrides.$name) { $values[$name] = [string]$config.overrides.$name }
|
|
if ($values[$name]) {
|
|
$uri = $null
|
|
if (-not [Uri]::TryCreate($values[$name], [UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -ne 'https') {
|
|
throw "config/downloads.json: $name must be an HTTPS URL."
|
|
}
|
|
}
|
|
}
|
|
# Explicit caller environment settings take priority over the selected preset.
|
|
if ($env:UV_DEFAULT_INDEX) { $values.python_index = $env:UV_DEFAULT_INDEX }
|
|
elseif ($env:UV_INDEX_URL) { $values.python_index = $env:UV_INDEX_URL }
|
|
if ($env:UV_PYTHON_INSTALL_MIRROR) { $values.python_download_mirror = $env:UV_PYTHON_INSTALL_MIRROR }
|
|
if ($env:npm_config_registry) { $values.npm_registry = $env:npm_config_registry }
|
|
if ($values.python_download_mirror) { $env:UV_PYTHON_INSTALL_MIRROR = $values.python_download_mirror }
|
|
if ($values.npm_registry) { $env:npm_config_registry = $values.npm_registry }
|
|
$env:UV_HTTP_TIMEOUT = [string]$timeout
|
|
$env:npm_config_fetch_timeout = [string]($timeout * 1000)
|
|
$values.mode = $Mode
|
|
$values.timeout_seconds = $timeout
|
|
$script:ArtemisDownloads = [pscustomobject]$values
|
|
Write-Host " [INFO] Download mode: $Mode (config/downloads.json)." -ForegroundColor Cyan
|
|
}
|
|
|
|
function Install-ArtemisUvFromIndex {
|
|
param([string]$IndexUrl, [string]$InstallDir = (Join-Path $env:USERPROFILE '.local\bin'))
|
|
# A uv wheel contains the standalone executable; no preinstalled Python is needed.
|
|
$work = Join-Path ([IO.Path]::GetTempPath()) ('artemis-uv-' + [guid]::NewGuid().ToString('N'))
|
|
New-Item -ItemType Directory -Path $work -ErrorAction Stop | Out-Null
|
|
try {
|
|
$index = $IndexUrl.TrimEnd('/') + '/uv/'
|
|
$page = Join-Path $work 'index.html'
|
|
if (-not (Invoke-DownloadFile -Uri $index -OutFile $page)) { throw 'Could not download the uv package index.' }
|
|
$platform = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { 'win_arm64' } else { 'win_amd64' }
|
|
$pattern = 'href=["''](?<link>[^"'']*uv-(?<version>\d+\.\d+\.\d+)-py3-none-' + $platform + '\.whl#sha256=(?<hash>[a-fA-F0-9]{64}))["'']'
|
|
$candidates = [regex]::Matches((Get-Content -LiteralPath $page -Raw), $pattern)
|
|
$candidate = $candidates | Sort-Object { [version]$_.Groups['version'].Value } -Descending | Select-Object -First 1
|
|
if (-not $candidate) { throw "No stable uv wheel for $platform was found on the selected index." }
|
|
$url = [Uri]::new([Uri]$index, [System.Net.WebUtility]::HtmlDecode($candidate.Groups['link'].Value))
|
|
if ($url.Scheme -ne 'https') { throw 'The uv download must use HTTPS.' }
|
|
$zip = Join-Path $work 'uv.zip'
|
|
if (-not (Invoke-DownloadFile -Uri $url.AbsoluteUri.Split('#')[0] -OutFile $zip)) { throw 'Could not download uv from the selected index.' }
|
|
$hasher = [Security.Cryptography.SHA256]::Create()
|
|
$stream = [IO.File]::OpenRead($zip)
|
|
try { $digest = [BitConverter]::ToString($hasher.ComputeHash($stream)).Replace('-', '') }
|
|
finally { $stream.Dispose(); $hasher.Dispose() }
|
|
if ($digest -ne $candidate.Groups['hash'].Value) {
|
|
throw 'The downloaded uv package failed SHA256 verification.'
|
|
}
|
|
$extract = Join-Path $work 'wheel'
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
[IO.Compression.ZipFile]::ExtractToDirectory($zip, $extract)
|
|
$executable = Get-ChildItem -LiteralPath $extract -Filter uv.exe -Recurse -File | Select-Object -First 1
|
|
if (-not $executable) { throw 'The uv package did not contain uv.exe.' }
|
|
New-Item -ItemType Directory -Path $InstallDir -Force -ErrorAction Stop | Out-Null
|
|
Copy-Item -LiteralPath $executable.FullName -Destination (Join-Path $InstallDir 'uv.exe') -Force -ErrorAction Stop
|
|
$uvx = Join-Path $executable.DirectoryName 'uvx.exe'
|
|
if (Test-Path -LiteralPath $uvx) { Copy-Item -LiteralPath $uvx -Destination $InstallDir -Force -ErrorAction Stop }
|
|
$env:PATH = "$InstallDir;$env:PATH"
|
|
} finally {
|
|
$resolved = (Resolve-Path -LiteralPath $work).Path
|
|
$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
|
|
if ($resolved.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -and (Split-Path $resolved -Leaf) -like 'artemis-uv-*') {
|
|
Remove-Item -LiteralPath $resolved -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
function Install-ArtemisLockedDependencies {
|
|
param([string]$PythonExe, [string]$IndexUrl)
|
|
# uv sync uses the original artifact URLs in uv.lock. Export exact versions and
|
|
# hashes, then let uv pip fetch those same artifacts through the chosen index.
|
|
$requirements = Join-Path ([IO.Path]::GetTempPath()) ('artemis-requirements-' + [guid]::NewGuid().ToString('N') + '.txt')
|
|
try {
|
|
& uv export --frozen --no-dev --format requirements-txt --output-file $requirements 1>$null
|
|
if ($LASTEXITCODE -ne 0) { throw 'Could not export the locked dependencies.' }
|
|
& uv pip install --python $PythonExe --index-url $IndexUrl --requirements $requirements | Out-Host
|
|
if ($LASTEXITCODE -ne 0) { throw 'Dependency installation failed. Check mirror availability or select another source in config/downloads.json.' }
|
|
} finally {
|
|
Remove-Item -LiteralPath $requirements -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|