fix: ship updatable 1.1.1 source archives

Package a clean shallow main checkout with credential-free Gitea remote, localize the China launcher, and set the new-install Responses endpoint.
This commit is contained in:
chenmingxuan committed 2026-09-24 10:49:57 +08:00
1 parent bfcd828fa8
commit 8522fa3ca3
22 files changed
+445 -140

No files matched your search

+27 -21
View File
@@ -1,42 +1,48 @@
# Python bootstrap shared by the Windows launcher and its isolated checks.
# Keep this file ASCII for Windows PowerShell 5.1.
# Python bootstrap shared by the Windows launcher and its isolated checks.
# Save as UTF-8 with BOM so Windows PowerShell 5.1 reads Chinese messages correctly.
function Get-BootstrapText {
param([string]$English, [string]$Chinese)
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN') { return $Chinese }
return $English
}
function Initialize-ArtemisPython {
param([Parameter(Mandatory=$true)][string]$ProjectRoot)
$ProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot -ErrorAction Stop).Path
if (-not (Test-Path -LiteralPath (Join-Path $ProjectRoot 'pyproject.toml'))) {
throw 'pyproject.toml is missing. Extract the complete source archive first.'
throw (Get-BootstrapText 'pyproject.toml is missing. Extract the complete source archive first.' '缺少 pyproject.toml;请重新解压完整源码包。')
}
if (-not (Test-Path -LiteralPath (Join-Path $ProjectRoot 'uv.lock'))) {
throw 'uv.lock is missing. Extract the complete source archive first.'
throw (Get-BootstrapText 'uv.lock is missing. Extract the complete source archive first.' '缺少 uv.lock;请重新解压完整源码包。')
}
if (-not (Test-CommandExists 'uv')) {
if ($ArtemisDownloads -and $ArtemisDownloads.python_index) {
Write-Host ' [INSTALL] Installing uv from the selected Python mirror...' -ForegroundColor Cyan
Write-Host (Get-BootstrapText ' [INSTALL] Installing uv from the selected Python mirror...' ' [安装] 正在从选定的 Python 镜像安装 uv...') -ForegroundColor Cyan
Install-ArtemisUvFromIndex -IndexUrl $ArtemisDownloads.python_index
} else {
Write-Host ' [INSTALL] uv was not found. Installing from the configured installer...' -ForegroundColor Cyan
Write-Host (Get-BootstrapText ' [INSTALL] uv was not found. Installing from the configured installer...' ' [安装] 未找到 uv,正在下载安装...') -ForegroundColor Cyan
$installer = Join-Path ([IO.Path]::GetTempPath()) ('artemis-uv-' + [guid]::NewGuid().ToString('N') + '.ps1')
try {
$installerUrl = if ($ArtemisDownloads) { $ArtemisDownloads.uv_installer_url } else { 'https://astral.sh/uv/install.ps1' }
if (-not (Invoke-DownloadFile -Uri $installerUrl -OutFile $installer)) {
throw 'Could not download uv. Check your network/proxy and retry.'
throw (Get-BootstrapText 'Could not download uv. Check your network/proxy and retry.' 'uv 下载失败;请检查网络、代理或镜像后重试。')
}
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer | Out-Host
if ($LASTEXITCODE -ne 0) { throw "uv installation failed (exit $LASTEXITCODE)." }
if ($LASTEXITCODE -ne 0) { throw (Get-BootstrapText "uv installation failed (exit $LASTEXITCODE)." "uv 安装失败(退出码 $LASTEXITCODE)。") }
} finally {
Remove-Item -LiteralPath $installer -Force -ErrorAction SilentlyContinue
}
}
Update-EnvironmentPath
} else {
Write-Host ' [SKIP] uv is already installed.' -ForegroundColor Green
Write-Host (Get-BootstrapText ' [SKIP] uv is already installed.' ' [复用] uv 已安装,跳过下载。') -ForegroundColor Green
}
if (-not (Test-CommandExists 'uv')) { throw 'uv is still unavailable. Install it from https://astral.sh/uv/ and retry.' }
if (-not (Test-CommandExists 'uv')) { throw (Get-BootstrapText 'uv is still unavailable. Install it from https://astral.sh/uv/ and retry.' '仍无法运行 uv;请检查安装并重试。') }
& uv --version | Out-Host
if ($LASTEXITCODE -ne 0) { throw 'The installed uv could not run. Repair its installation and retry.' }
if ($LASTEXITCODE -ne 0) { throw (Get-BootstrapText 'The installed uv could not run. Repair its installation and retry.' '已安装的 uv 无法运行;请修复安装后重试。') }
$venvDir = Join-Path $ProjectRoot '.venv'
$pythonExe = Join-Path $venvDir 'Scripts\python.exe'
@@ -45,18 +51,18 @@ function Initialize-ArtemisPython {
Push-Location -LiteralPath $ProjectRoot
try {
if (-not (Test-Path -LiteralPath $venvDir)) {
Write-Host ' [INSTALL] Creating .venv with Python 3.12 (downloaded automatically if needed)...' -ForegroundColor Cyan
Write-Host (Get-BootstrapText ' [INSTALL] Creating .venv with Python 3.12 (downloaded automatically if needed)...' ' [安装] 正在创建 Python 3.12 虚拟环境 .venv;缺少 Python 时自动下载...') -ForegroundColor Cyan
& uv venv --python 3.12 $venvDir | Out-Host
if ($LASTEXITCODE -ne 0) { throw 'Could not create .venv. Check the download errors above and retry.' }
if ($LASTEXITCODE -ne 0) { throw (Get-BootstrapText 'Could not create .venv. Check the download errors above and retry.' '.venv 创建失败;请查看上方下载错误并重试。') }
} else {
Write-Host ' [SKIP] Reusing the existing project .venv.' -ForegroundColor Green
Write-Host (Get-BootstrapText ' [SKIP] Reusing the existing project .venv.' ' [复用] 已有项目 .venv,跳过创建。') -ForegroundColor Green
}
if (-not (Test-Path -LiteralPath $pythonExe)) {
throw 'The existing .venv is incomplete. Rename it as a backup, then rerun start.bat.'
throw (Get-BootstrapText 'The existing .venv is incomplete. Rename it as a backup, then rerun start.bat.' '已有 .venv 不完整。先重命名备份,再运行 start-cn.bat。')
}
& $pythonExe -c 'import sys; sys.exit(0 if sys.version_info >= (3, 12) and sys.prefix != sys.base_prefix else 1)' | Out-Host
if ($LASTEXITCODE -ne 0) {
throw 'The existing .venv is unusable or older than Python 3.12. Rename it as a backup, then rerun start.bat.'
throw (Get-BootstrapText 'The existing .venv is unusable or older than Python 3.12. Rename it as a backup, then rerun start.bat.' '已有 .venv 无法使用或 Python 低于 3.12。先重命名备份,再运行 start-cn.bat。')
}
# A cheap check skips installation when the lock and installed packages agree.
@@ -64,19 +70,19 @@ function Initialize-ArtemisPython {
$lockOption = if ($ArtemisDownloads -and $ArtemisDownloads.python_index) { '--frozen' } else { '--locked' }
& uv sync --check $lockOption --no-dev --inexact --quiet 2>$null | Out-Host
if ($LASTEXITCODE -eq 0) {
Write-Host ' [SKIP] Python dependencies are already ready.' -ForegroundColor Green
Write-Host (Get-BootstrapText ' [SKIP] Python dependencies are already ready.' ' [复用] Python 依赖已就绪。') -ForegroundColor Green
} else {
Write-Host ' [INSTALL] Synchronizing dependencies from uv.lock...' -ForegroundColor Cyan
Write-Host (Get-BootstrapText ' [INSTALL] Synchronizing dependencies from uv.lock...' ' [安装] 正在按 uv.lock 同步 Python 依赖...') -ForegroundColor Cyan
if ($ArtemisDownloads -and $ArtemisDownloads.python_index) {
Install-ArtemisLockedDependencies -PythonExe $pythonExe -IndexUrl $ArtemisDownloads.python_index
} else {
& uv sync --locked --no-dev --inexact | Out-Host
if ($LASTEXITCODE -ne 0) { throw 'Dependency installation failed. Fix the error above and rerun start.bat.' }
if ($LASTEXITCODE -ne 0) { throw (Get-BootstrapText 'Dependency installation failed. Fix the error above and rerun start.bat.' '依赖安装失败;请处理上方错误,再运行 start-cn.bat。') }
}
}
& $pythonExe -c 'import artemis, uvicorn, fastapi, langchain_openai' | Out-Host
if ($LASTEXITCODE -ne 0) { throw 'Python dependency verification failed. The UI will not be started.' }
Write-Host ' [OK] Python environment is ready.' -ForegroundColor Green
if ($LASTEXITCODE -ne 0) { throw (Get-BootstrapText 'Python dependency verification failed. The UI will not be started.' 'Python 依赖验证失败,网页不会启动。') }
Write-Host (Get-BootstrapText ' [OK] Python environment is ready.' ' [完成] Python 环境已就绪。') -ForegroundColor Green
return $pythonExe
} finally {
Pop-Location
+7 -3
View File
@@ -1,5 +1,5 @@
# Download settings are process-local; never edit global pip/npm/uv settings.
# Keep this file ASCII for Windows PowerShell 5.1.
# Download settings are process-local; never edit global pip/npm/uv settings.
# Save as UTF-8 with BOM so Windows PowerShell 5.1 reads Chinese messages correctly.
function Initialize-ArtemisDownloads {
param([string]$ProjectRoot, [string]$Mode)
@@ -42,7 +42,11 @@ function Initialize-ArtemisDownloads {
# Official scrcpy v4.1 win64 release digest. Verify every mirror/override equally.
$values.scrcpy_sha256 = '5b12172b3264b2889f4583ee64752ce832e29bc8b1089dca81093459697165db'
$script:ArtemisDownloads = [pscustomobject]$values
Write-Host " [INFO] Download mode: $Mode (config/downloads.json)." -ForegroundColor Cyan
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN') {
Write-Host " [镜像] 使用国内下载源(config/downloads.json)。" -ForegroundColor Cyan
} else {
Write-Host " [INFO] Download mode: $Mode (config/downloads.json)." -ForegroundColor Cyan
}
}
function Install-ArtemisUvFromIndex {
+111 -11
View File
@@ -1,10 +1,13 @@
"""Create a portable source ZIP without environments, builds, or local credentials."""
"""Create a clean, shallow Git checkout ZIP that can fast-forward on launch."""
import argparse
from datetime import datetime
import hashlib
import os
from pathlib import Path
import subprocess
import tempfile
from urllib.parse import urlsplit
import zipfile
@@ -83,6 +86,9 @@ EXCLUDED_SUFFIXES = {
".swo",
}
GIT_METADATA_FILES = {"HEAD", "config", "index", "shallow", "packed-refs"}
GIT_METADATA_DIRS = {"objects", "refs"}
def excluded(path: Path) -> bool:
parts = tuple(part.lower() for part in path.parts)
@@ -116,22 +122,116 @@ def source_files(root: Path):
yield path
def git(root: Path, *args: str) -> str:
result = subprocess.run(
["git", "-C", str(root), *args],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=90,
)
if result.returncode:
raise RuntimeError(f"git {args[0]} failed (exit {result.returncode})")
return result.stdout.strip()
def git_metadata_files(root: Path):
metadata = root / ".git"
for name in sorted(GIT_METADATA_FILES):
path = metadata / name
if path.is_file():
yield path
for name in sorted(GIT_METADATA_DIRS):
directory = metadata / name
if directory.exists():
yield from source_files_without_filter(directory)
for name in ("exclude", "sparse-checkout"):
path = metadata / "info" / name
if path.is_file():
yield path
def source_files_without_filter(root: Path):
for directory, dirs, files in os.walk(root, followlinks=False):
current = Path(directory)
dirs[:] = sorted(name for name in dirs if not (current / name).is_symlink())
for name in sorted(files):
path = current / name
if not path.is_symlink():
yield path
def release_remote(root: Path) -> tuple[str, str]:
if not (root / ".git").exists():
raise ValueError("Source packaging requires a Git checkout")
if git(root, "status", "--porcelain", "--untracked-files=no"):
raise ValueError("Commit tracked changes before packaging a release")
branch = git(root, "symbolic-ref", "--quiet", "--short", "HEAD")
remote = git(root, "config", "--get", f"branch.{branch}.remote")
merge_ref = git(root, "config", "--get", f"branch.{branch}.merge")
if remote == "." or merge_ref != f"refs/heads/{branch}":
raise ValueError("The current branch must track the same branch on a remote")
url = git(root, "remote", "get-url", remote)
parsed = urlsplit(url)
if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password:
raise ValueError("The release upstream must be a credential-free HTTPS URL")
if parsed.query or parsed.fragment:
raise ValueError("The release upstream must not contain a query or fragment")
return branch, url
def package_source(root: Path, output: Path) -> tuple[int, str]:
root = root.resolve()
output = output.resolve()
branch, remote_url = release_remote(root)
revision = git(root, "rev-parse", "HEAD")
output.parent.mkdir(parents=True, exist_ok=True)
temporary = output.with_suffix(".zip.tmp")
files = [p for p in source_files(root) if p.resolve() not in {output, temporary}]
try:
with zipfile.ZipFile(temporary, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
for path in files:
archive.write(path, Path("artemis") / path.relative_to(root))
with zipfile.ZipFile(temporary) as archive:
bad_file = archive.testzip()
if bad_file:
raise RuntimeError(f"ZIP integrity check failed: {bad_file}")
if any(excluded(Path(name).relative_to("artemis")) for name in archive.namelist()):
raise RuntimeError("An excluded file was found in the source archive")
with tempfile.TemporaryDirectory(prefix="artemis-release-") as work:
checkout = Path(work) / "artemis"
git(
root,
"clone",
"--depth",
"1",
"--single-branch",
"--branch",
branch,
root.as_uri(),
str(checkout),
)
git(checkout, "remote", "set-url", "origin", remote_url)
# Generated assets are tracked upstream but must not appear in the
# unpacked source. Sparse checkout keeps the Git index clean.
omitted = [
name for name in git(checkout, "ls-files").splitlines() if excluded(Path(name))
]
patterns = ["/*", *(f"!/{name}" for name in omitted)]
git(checkout, "sparse-checkout", "set", "--no-cone", *patterns)
# Rebuilt assets in sparse paths must not look like user edits.
info_exclude = checkout / ".git" / "info" / "exclude"
with info_exclude.open("a", encoding="utf-8") as stream:
stream.write("\n".join(["", *(f"/{name}" for name in omitted), ""]))
if git(checkout, "rev-parse", "HEAD") != revision or git(
checkout, "status", "--porcelain"
):
raise RuntimeError("The packaged checkout is not clean at the requested revision")
files = [*source_files(checkout), *git_metadata_files(checkout)]
names = {
str(Path("artemis") / path.relative_to(checkout)).replace("\\", "/")
for path in files
}
with zipfile.ZipFile(temporary, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as archive:
for path in files:
archive.write(path, Path("artemis") / path.relative_to(checkout))
with zipfile.ZipFile(temporary) as archive:
bad_file = archive.testzip()
if bad_file or set(archive.namelist()) != names:
raise RuntimeError(
f"ZIP integrity check failed: {bad_file or 'file list mismatch'}"
)
temporary.replace(output)
finally:
temporary.unlink(missing_ok=True)
+44 -38
View File
@@ -1,4 +1,4 @@
# Copyright 2026 Google LLC
# 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.
@@ -24,6 +24,12 @@ param(
$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
}
# 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
@@ -49,7 +55,7 @@ if ((-not $SkipUpdate) -and (Test-Path -LiteralPath $existingPython)) {
exit $LASTEXITCODE
}
if ($updateExit -ne 0) {
Write-Host ' [ERROR] Source update check failed. Resolve the error above, or use -SkipUpdate for local startup.' -ForegroundColor Red
Write-Host (Get-LaunchText ' [ERROR] Source update check failed. Resolve the error above, or use -SkipUpdate for local startup.' ' [错误] 源码更新检查失败。请处理上方错误;要运行当前源码可传入 -SkipUpdate。') -ForegroundColor Red
exit 1
}
}
@@ -58,12 +64,12 @@ if ((-not $SkipUpdate) -and (Test-Path -LiteralPath $existingPython)) {
try {
Initialize-ArtemisDownloads -ProjectRoot $RootDir -Mode $Mirror
} catch {
Write-Host " [ERROR] Invalid download settings: $_" -ForegroundColor Red
Write-Host (Get-LaunchText " [ERROR] Invalid download settings: $_" " [错误] 下载配置无效:$_") -ForegroundColor Red
exit 1
}
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " Artemis Autonomous Mobile Agent UI " -ForegroundColor Cyan
Write-Host (Get-LaunchText " Artemis Autonomous Mobile Agent UI " " ARTEMIS 手机自动化测试平台") -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
@@ -181,7 +187,7 @@ function Install-PortablePlatformTools {
$env:PATH = "$ptDir;$env:PATH"
return $true
}
Write-Host " [INFO] Installing Android platform-tools (adb) in user space..." -ForegroundColor Cyan
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) {
@@ -190,12 +196,12 @@ function Install-PortablePlatformTools {
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
Write-Host (Get-LaunchText " [OK] adb installed in user space." " [完成] adb 已安装。") -ForegroundColor Green
return $true
}
}
} catch {
Write-Host " [WARN] Failed to install portable platform-tools: $_" -ForegroundColor DarkYellow
Write-Host (Get-LaunchText " [WARN] Failed to install portable platform-tools: $_" " [提示] adb 安装失败:$_") -ForegroundColor DarkYellow
}
return $false
}
@@ -217,9 +223,9 @@ function Install-PortableScrcpy {
}
}
Write-Host " [INFO] Installing portable scrcpy in user space..." -ForegroundColor Cyan
Write-Host (Get-LaunchText " [INFO] Installing portable scrcpy in user space..." " [安装] 正在安装 scrcpy(当前用户)...") -ForegroundColor Cyan
if ($ArtemisDownloads.mode -eq 'China' -and $ArtemisDownloads.scrcpy_url.StartsWith('https://github.com/')) {
Write-Host ' [WARN] China mode has no non-GitHub scrcpy source. Configure overrides.scrcpy_url or install scrcpy manually.' -ForegroundColor Yellow
Write-Host (Get-LaunchText ' [WARN] China mode has no non-GitHub scrcpy source. Configure overrides.scrcpy_url or install scrcpy manually.' ' [提示] 国内镜像中没有可用的 scrcpy 下载地址;请设置 config/downloads.json 的 overrides.scrcpy_url,或自行安装。') -ForegroundColor Yellow
return $false
}
try {
@@ -244,12 +250,12 @@ function Install-PortableScrcpy {
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
Write-Host (Get-LaunchText " [OK] Portable scrcpy installed in user space." " [完成] scrcpy 已安装。") -ForegroundColor Green
return $true
}
}
} catch {
Write-Host " [WARN] Failed to install portable scrcpy: $_" -ForegroundColor DarkYellow
Write-Host (Get-LaunchText " [WARN] Failed to install portable scrcpy: $_" " [提示] scrcpy 安装失败:$_") -ForegroundColor DarkYellow
}
return $false
}
@@ -295,7 +301,7 @@ function Install-PortableNode {
}
}
Write-Host " [INFO] Installing portable Node.js LTS (v22.23.2) in user space..." -ForegroundColor Cyan
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) {
@@ -319,12 +325,12 @@ function Install-PortableNode {
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
Write-Host (Get-LaunchText " [OK] Portable Node.js $nodeVer installed in user space." " [完成] Node.js $nodeVer 已安装。") -ForegroundColor Green
return $true
}
}
} catch {
Write-Host " [WARN] Failed to install portable Node.js: $_" -ForegroundColor DarkYellow
Write-Host (Get-LaunchText " [WARN] Failed to install portable Node.js: $_" " [提示] Node.js 安装失败:$_") -ForegroundColor DarkYellow
}
return $false
}
@@ -344,16 +350,16 @@ try {
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
Write-Host (Get-LaunchText ' [OK] Created .env from .env.example. Configure your model in the web UI.' ' [完成] 已按模板创建 .env;请在网页中配置模型。') -ForegroundColor Green
}
}
Write-Host ' [CHECK] Refreshing already-configured MCP clients...' -ForegroundColor Cyan
Write-Host (Get-LaunchText ' [CHECK] Refreshing already-configured MCP clients...' ' [检查] 正在检查并更新已有的 MCP 客户端配置...') -ForegroundColor Cyan
& $ArtemisPython (Join-Path $ScriptDir 'update_installation.py') mcp
if ($LASTEXITCODE -ne 0) {
Write-Host ' [WARN] MCP refresh failed. Existing client settings were not replaced; check the errors above.' -ForegroundColor Yellow
Write-Host (Get-LaunchText ' [WARN] MCP refresh failed. Existing client settings were not replaced; check the errors above.' ' [提示] MCP 配置更新失败,原配置仍保留;请查看上方错误。') -ForegroundColor Yellow
}
if ($SetupOnly) {
Write-Host ' [OK] Setup complete. Run start.bat to launch Artemis.' -ForegroundColor Green
Write-Host (Get-LaunchText ' [OK] Setup complete. Run start.bat to launch Artemis.' ' [完成] 环境已准备好。运行 start-cn.bat 启动 ARTEMIS。') -ForegroundColor Green
exit 0
}
@@ -371,8 +377,8 @@ 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]"
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
}
@@ -382,7 +388,7 @@ if ($missingCore.Count -gt 0) {
}
if ($useWinGet) {
Write-Host " [INFO] Auto-installing missing components ($($missingCore -join ', '))..." -ForegroundColor DarkYellow
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
}
@@ -401,7 +407,7 @@ if ($missingCore.Count -gt 0) {
}
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
Write-Host (Get-LaunchText ' [WARN] scrcpy download failed. Set overrides.scrcpy_url in config/downloads.json to a reachable release ZIP, then retry.' ' [提示] scrcpy 下载失败。请在 config/downloads.json 中设置可访问的 overrides.scrcpy_url,然后重试。') -ForegroundColor Yellow
}
}
}
@@ -415,19 +421,19 @@ $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 ' [ERROR] Could not check frontend sources.' -ForegroundColor Red; exit 1 }
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 " [INFO] Detected Node.js $curVer, but Angular CLI requires Node.js >= v22.22.0." -ForegroundColor Yellow
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 " [INFO] Node.js/npm not found (required for Showcase UI)." -ForegroundColor Cyan
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 " [INFO] Installing Node.js 22 LTS via nvm..." -ForegroundColor Cyan
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
@@ -438,8 +444,8 @@ if ($frontendChanged -or ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Pat
$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]"
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
}
@@ -449,7 +455,7 @@ if ($frontendChanged -or ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Pat
}
if ($useWinGetNode) {
Write-Host " [INFO] Installing/Upgrading Node.js LTS via WinGet..." -ForegroundColor Yellow
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
}
@@ -463,36 +469,36 @@ if ($frontendChanged -or ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Pat
if (Test-NodeCompatible) {
$nodeVer = (& node -v 2>$null)
Write-Host " [OK] Node.js $nodeVer is ready." -ForegroundColor Green
Write-Host " [INFO] Showcase UI sources/dependencies changed or build missing. Compiling..." -ForegroundColor Yellow
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 " [INFO] Installing frontend npm dependencies..." -ForegroundColor Cyan
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 " [INFO] Building Angular frontend application..." -ForegroundColor Cyan
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 " [OK] Showcase UI compiled successfully." -ForegroundColor Green
Write-Host (Get-LaunchText " [OK] Showcase UI compiled successfully." " [完成] 网页构建成功。") -ForegroundColor Green
} elseif ((Test-Path $ShowcaseIndexAlt1) -or (Test-Path $ShowcaseIndexAlt2)) {
Write-Host " [OK] Showcase UI compiled successfully." -ForegroundColor Green
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 " [ERROR] Failed to build Showcase UI: $_" -ForegroundColor Red
Write-Host (Get-LaunchText " [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
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
}
}
@@ -505,7 +511,7 @@ 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
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 {
+61 -13
View File
@@ -19,6 +19,10 @@ from urllib.parse import urlsplit
ROOT = Path(__file__).resolve().parents[1]
def launch_text(english: str, chinese: str) -> str:
return chinese if os.environ.get("ARTEMIS_LAUNCH_LANG") == "zh-CN" else english
def git(root: Path, *args: str, timeout: int = 15) -> str:
env = {**os.environ, "GIT_TERMINAL_PROMPT": "0", "GCM_INTERACTIVE": "Never"}
result = subprocess.run(
@@ -50,19 +54,28 @@ def update_source(root: Path, port: int = 8000) -> int:
or (status.get("status") == "running" and status.get("pid"))
):
print(
"[WAIT] Tasks are active. Finish/stop them before updating dependencies or restarting."
launch_text(
"[WAIT] Tasks are active. Finish/stop them before updating dependencies or restarting.",
"[等待] 有任务正在执行或排队。请先处理任务,再更新依赖或重启。",
)
)
return 20
except (OSError, ValueError):
pass
if not shutil.which("git"):
print(
"[SKIP] Git is not installed. Continuing with local source; install Git for automatic source updates."
launch_text(
"[SKIP] Git is not installed. Continuing with local source; install Git for automatic source updates.",
"[跳过] 未安装 Git,继续使用当前源码;安装 Git 后可自动检查更新。",
)
)
return 0
if not (root / ".git").exists():
print(
"[SKIP] This source ZIP has no Git metadata. Use a Git clone for automatic source updates; local files are preserved."
launch_text(
"[SKIP] This older source ZIP has no Git metadata. Use a new release ZIP or Git clone for automatic updates; local files are preserved.",
"[跳过] 旧版源码包不含 Git 信息。请使用新版源码包或 Git 克隆;当前文件保持不变。",
)
)
return 0
try:
@@ -79,32 +92,49 @@ def update_source(root: Path, port: int = 8000) -> int:
raise RuntimeError(
"Use a clean Git remote URL and a credential manager, not embedded credentials."
)
print("[CHECK] Checking the current branch's configured upstream...")
print(
launch_text(
"[CHECK] Checking the current branch's configured upstream...",
"[检查] 正在检查当前分支的远程更新...",
)
)
git(root, "fetch", "--no-tags", remote, merge_ref, timeout=45)
target = git(root, "rev-parse", "FETCH_HEAD")
current = git(root, "rev-parse", "HEAD")
if current == target:
print("[OK] Source is up to date.")
print(launch_text("[OK] Source is up to date.", "[完成] 源码已是最新版。"))
return 0
if git(root, "status", "--porcelain"):
print(
"[SKIP] Upstream differs, but local changes exist. Commit/back up your work before updating; continuing locally."
launch_text(
"[SKIP] Upstream differs, but local changes exist. Commit/back up your work before updating; continuing locally.",
"[跳过] 发现本地修改;请先提交或备份。此次继续使用本地源码。",
)
)
return 0
ancestor = git(root, "merge-base", "HEAD", target)
if ancestor != current:
print(
"[SKIP] Local branch is ahead or diverged. No automatic merge/rebase; continuing locally."
launch_text(
"[SKIP] Local branch is ahead or diverged. No automatic merge/rebase; continuing locally.",
"[跳过] 本地分支领先或分叉,不自动合并;继续使用本地源码。",
)
)
return 0
git(root, "merge", "--ff-only", target, timeout=30)
print(
f"[UPDATED] Source {current[:12]} -> {target[:12]}. Reloading launcher and checking dependencies."
launch_text(
f"[UPDATED] Source {current[:12]} -> {target[:12]}. Reloading launcher and checking dependencies.",
f"[已更新] 源码 {current[:12]} -> {target[:12]},重新载入启动脚本并检查依赖。",
)
)
return 10
except (RuntimeError, subprocess.TimeoutExpired) as exc:
print(
f"[WARN] {exc if isinstance(exc, RuntimeError) else 'Git update timed out.'} Continuing with local source."
launch_text(
f"[WARN] {exc if isinstance(exc, RuntimeError) else 'Git update timed out.'} Continuing with local source.",
"[提示] Git 更新失败或超时;请检查网络、凭据和仓库状态。继续使用当前源码。",
)
)
return 0
@@ -275,14 +305,32 @@ def refresh_mcp(root: Path) -> None:
else refresh_codex(path, root, sys.executable, ARTEMIS_MCP_TOOLS)
)
if updated:
print(f"[UPDATED] Existing MCP configuration: {path}")
print(
launch_text(
f"[UPDATED] Existing MCP configuration: {path}",
f"[已更新] 已有 MCP 配置:{path}",
)
)
changed += 1
except (OSError, ValueError, TypeError, AttributeError):
print(f"[WARN] Could not safely refresh {path}; original configuration preserved.")
print(
launch_text(
f"[WARN] Could not safely refresh {path}; original configuration preserved.",
f"[提示] 无法安全更新 {path};原配置已保留。",
)
)
print(
f"[OK] MCP source is current; refreshed {changed} existing client configuration(s). Reload the MCP connection in your AI client after a source update."
launch_text(
f"[OK] MCP source is current; refreshed {changed} existing client configuration(s). Reload the MCP connection in your AI client after a source update.",
f"[完成] MCP 源码已就绪;更新了 {changed} 个已有客户端配置。源码升级后请在 AI 客户端重新加载 MCP。",
)
)
print(
launch_text(
"[TIP] For a new client run: uv run artemis mcp --install <client>",
"[提示] 新客户端可执行:uv run artemis mcp --install <client>",
)
)
print("[TIP] For a new client run: uv run artemis mcp --install <client>")
def frontend_fingerprint(root: Path) -> str: