feat: introduce MCP server, cross-platform runtime, and Showcase UI reorganization
- Showcase UI Reorganization: * Move web UI dashboard from root directory into apps/showcase_ui/ for cleaner project structure * Add 'artemis ui' CLI command (artemis/interfaces/cli/commands/ui.py) and launcher scripts for the interactive showcase console - Standalone MCP Server & Notifiers (mcp_server/): * Implement dedicated MCP server package with mobile automation tools (device_state, inspect_trace, task_manager, task_runner) * Add multi-channel notifiers (AgentAPI, Desktop notifications, Webhooks, File output, and Composite routing) * Introduce background task runner and trace store for autonomous mobile execution monitoring - Cross-Platform Runtime & Toolchain Support: * Add artemis/platform/ with OS-specific implementations for Linux, macOS (Darwin), and Windows * Add artemis/runtime/supervisor.py for resilient process and agent runtime supervision * Add artemis/toolchain/ with dependency resolution and SDK descriptors * Add cross-platform launcher scripts (start.sh, start.bat) and installer scripts (scripts/install_deps.*) - Core Enhancements & Diagnostics: * Enhance coordinate normalization, OCR API fallback, and perception pipeline * Add credential validation (credentials_validator.py) and system diagnostics * Update bilingual documentation (README.md, README_CN.md) and architecture diagrams - Test Suite Expansion: * Add integration tests for cloud gateway, remote ADB, and Artemis login * Add unit tests covering core diagnostics, platform detection, toolchain, credentials validator, optional OCR, and MCP tools
This commit is contained in:
1 parent
78e478f502
commit
9a5aaf99b7
249 files changed
+37226
-9339
No files matched your search
@@ -0,0 +1,162 @@
|
||||
# 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.
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Artemis Smart Multi-Platform Dependency Installer for Windows (PowerShell).
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Launch = $false,
|
||||
[switch]$Open = $false
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
Write-Host "======================================================" -ForegroundColor Cyan
|
||||
Write-Host " 🚀 Artemis - Windows Smart Installer " -ForegroundColor Cyan
|
||||
Write-Host "======================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$RootDir = Split-Path -Parent $ScriptDir
|
||||
Set-Location $RootDir
|
||||
|
||||
function Update-EnvironmentPath {
|
||||
$standardDirs = @(
|
||||
"$env:LOCALAPPDATA\Microsoft\WinGet\Links",
|
||||
"$env:LOCALAPPDATA\Android\Sdk\platform-tools",
|
||||
"$env:ProgramFiles\Android\platform-tools",
|
||||
"C:\ProgramData\chocolatey\bin",
|
||||
"$env:USERPROFILE\scoop\shims",
|
||||
"$env:USERPROFILE\.local\bin",
|
||||
"$env:USERPROFILE\.cargo\bin"
|
||||
)
|
||||
$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 ";"
|
||||
}
|
||||
|
||||
function Test-CommandExists {
|
||||
param([string]$Command)
|
||||
$res = Get-Command $Command -ErrorAction SilentlyContinue
|
||||
return ($null -ne $res)
|
||||
}
|
||||
|
||||
# 0. Initial PATH refresh
|
||||
Update-EnvironmentPath
|
||||
|
||||
# 1. Check System Dependencies (ADB, FFmpeg, scrcpy)
|
||||
Write-Host "1. Checking System Toolchains (ADB, FFmpeg, scrcpy)..." -ForegroundColor Yellow
|
||||
$missingTools = @()
|
||||
|
||||
if (-not (Test-CommandExists "adb")) { $missingTools += "adb" }
|
||||
if (-not (Test-CommandExists "ffmpeg")) { $missingTools += "ffmpeg" }
|
||||
if (-not (Test-CommandExists "scrcpy")) { $missingTools += "scrcpy" }
|
||||
|
||||
if ($missingTools.Count -eq 0) {
|
||||
Write-Host " ✔ All system toolchains are already installed (ADB, FFmpeg, scrcpy)." -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ! Missing toolchains: $($missingTools -join ', ')" -ForegroundColor DarkYellow
|
||||
Write-Host " Attempting automatic installation via Windows Package Manager..." -ForegroundColor Gray
|
||||
|
||||
if (Test-CommandExists "winget") {
|
||||
Write-Host " Detected WinGet. Installing packages..." -ForegroundColor Cyan
|
||||
if ($missingTools -contains "adb") {
|
||||
Write-Host " 📦 Installing Google.PlatformTools (ADB)..." -ForegroundColor Cyan
|
||||
winget install --id Google.PlatformTools -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
||||
}
|
||||
if ($missingTools -contains "ffmpeg") {
|
||||
Write-Host " 📦 Installing Gyan.FFmpeg..." -ForegroundColor Cyan
|
||||
winget install --id Gyan.FFmpeg -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
||||
}
|
||||
if ($missingTools -contains "scrcpy") {
|
||||
Write-Host " 📦 Installing Genymobile.scrcpy..." -ForegroundColor Cyan
|
||||
winget install --id Genymobile.scrcpy -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
|
||||
}
|
||||
} elseif (Test-CommandExists "choco") {
|
||||
Write-Host " Detected Chocolatey. Installing packages..." -ForegroundColor Cyan
|
||||
choco install ($missingTools -join " ") -y | Out-Null
|
||||
} elseif (Test-CommandExists "scoop") {
|
||||
Write-Host " Detected Scoop. Installing packages..." -ForegroundColor Cyan
|
||||
scoop install ($missingTools -join " ") | Out-Null
|
||||
}
|
||||
|
||||
Update-EnvironmentPath
|
||||
}
|
||||
|
||||
# 2. Check and Install uv (Fast Python Package Manager)
|
||||
Write-Host "`n2. Checking Python Package Manager (uv)..." -ForegroundColor Yellow
|
||||
if (-not (Test-CommandExists "uv")) {
|
||||
Write-Host " uv not found. Installing Astral uv..." -ForegroundColor Cyan
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
Update-EnvironmentPath
|
||||
}
|
||||
|
||||
if (Test-CommandExists "uv") {
|
||||
$uvVer = uv --version
|
||||
Write-Host " ✔ uv is ready ($uvVer)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ✗ Failed to detect uv in PATH." -ForegroundColor Red
|
||||
Exit 1
|
||||
}
|
||||
|
||||
# 3. Setup Python Runtime & Sync Dependencies
|
||||
Write-Host "`n3. Configuring Python Runtime & Syncing Dependencies..." -ForegroundColor Yellow
|
||||
Write-Host " 📦 Running uv sync (auto-provisioning Python >=3.12 & dependencies)..." -ForegroundColor Cyan
|
||||
uv sync
|
||||
Write-Host " ✔ Dependencies synced successfully." -ForegroundColor Green
|
||||
|
||||
# 4. Check .env Configuration
|
||||
Write-Host "`n4. Checking Environment Configuration (.env)..." -ForegroundColor Yellow
|
||||
if (-not (Test-Path ".env")) {
|
||||
if (Test-Path ".env.example") {
|
||||
Copy-Item ".env.example" ".env"
|
||||
Write-Host " ✔ Created .env template from .env.example." -ForegroundColor Green
|
||||
} else {
|
||||
New-Item -ItemType File -Path ".env" | Out-Null
|
||||
Write-Host " ✔ Created empty .env file." -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host " ✔ .env configuration file exists." -ForegroundColor Green
|
||||
}
|
||||
|
||||
# 5. Toolchain Readiness Summary
|
||||
Write-Host "`n5. Toolchain Readiness Summary:" -ForegroundColor Yellow
|
||||
$tools = @("adb", "ffmpeg", "scrcpy", "uv")
|
||||
foreach ($t in $tools) {
|
||||
if (Test-CommandExists $t) {
|
||||
$p = (Get-Command $t).Source
|
||||
Write-Host " ✔ $t -> $p" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ○ $t -> Not found in PATH (Fallbacks active)" -ForegroundColor DarkYellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "======================================================" -ForegroundColor Cyan
|
||||
Write-Host " ✨ Artemis Environment Ready! " -ForegroundColor Green
|
||||
Write-Host "======================================================" -ForegroundColor Cyan
|
||||
|
||||
if ($Launch -or $Open) {
|
||||
Write-Host "🚀 Launching Showcase UI..." -ForegroundColor Green
|
||||
uv run artemis ui --open
|
||||
} else {
|
||||
Write-Host "To start the Showcase UI and interactive onboarding:" -ForegroundColor White
|
||||
Write-Host " 👉 .\start.bat (or: uv run artemis ui)" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Visual styling
|
||||
BOLD='\033[1m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
DIM='\033[2m'
|
||||
NC='\033[0m'
|
||||
|
||||
AUTO_LAUNCH=false
|
||||
for arg in "$@"; do
|
||||
case "${arg}" in
|
||||
--launch|-l|--open)
|
||||
AUTO_LAUNCH=true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo -e "${BOLD}${CYAN}======================================================${NC}"
|
||||
echo -e "${BOLD}${CYAN} 🚀 Artemis - Smart Multi-Platform Installer ${NC}"
|
||||
echo -e "${BOLD}${CYAN}======================================================${NC}"
|
||||
echo ""
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${ROOT_DIR}"
|
||||
|
||||
# Normalize PATH with standard platform binary paths
|
||||
STANDARD_PATHS=(
|
||||
"/opt/homebrew/bin"
|
||||
"/opt/homebrew/sbin"
|
||||
"/usr/local/bin"
|
||||
"/usr/local/sbin"
|
||||
"${HOME}/.local/bin"
|
||||
"${HOME}/.cargo/bin"
|
||||
"${HOME}/Library/Android/sdk/platform-tools"
|
||||
"${HOME}/Android/Sdk/platform-tools"
|
||||
)
|
||||
for p in "${STANDARD_PATHS[@]}"; do
|
||||
if [ -d "${p}" ] && [[ ":${PATH}:" != *":${p}:"* ]]; then
|
||||
export PATH="${p}:${PATH}"
|
||||
fi
|
||||
done
|
||||
|
||||
OS_TYPE="$(uname -s)"
|
||||
ARCH_TYPE="$(uname -m)"
|
||||
|
||||
# Helper to check command availability
|
||||
has_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
echo -e "${BOLD}1. Detecting Operating System & Environment...${NC}"
|
||||
echo -e " Platform: ${BLUE}${OS_TYPE}${NC} (${ARCH_TYPE})"
|
||||
echo -e " Root Dir: ${DIM}${ROOT_DIR}${NC}"
|
||||
|
||||
# Function to install system toolchains (ADB, FFmpeg, scrcpy)
|
||||
install_system_packages() {
|
||||
echo -e "\n${BOLD}2. Checking System Toolchains (ADB, FFmpeg, scrcpy)...${NC}"
|
||||
|
||||
local need_adb=false
|
||||
local need_ffmpeg=false
|
||||
local need_scrcpy=false
|
||||
|
||||
if ! has_cmd adb; then need_adb=true; fi
|
||||
if ! has_cmd ffmpeg; then need_ffmpeg=true; fi
|
||||
if ! has_cmd scrcpy; then need_scrcpy=true; fi
|
||||
|
||||
if [ "${need_adb}" = false ] && [ "${need_ffmpeg}" = false ] && [ "${need_scrcpy}" = false ]; then
|
||||
echo -e " ${GREEN}✓ All core system tools are already installed.${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e " ${YELLOW}! Missing toolchains detected:${NC}"
|
||||
[ "${need_adb}" = true ] && echo -e " • [ADB] Android Debug Bridge"
|
||||
[ "${need_ffmpeg}" = true ] && echo -e " • [FFmpeg] Video encoding/trimming framework"
|
||||
[ "${need_scrcpy}" = true ] && echo -e " • [scrcpy] Real-time Android screen mirroring"
|
||||
|
||||
echo -e " Attempting automated installation..."
|
||||
|
||||
if [ "${OS_TYPE}" = "Darwin" ]; then
|
||||
# Ensure Homebrew environment variables to prevent hanging
|
||||
export HOMEBREW_NO_AUTO_UPDATE=1
|
||||
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
export HOMEBREW_NO_ENV_HINTS=1
|
||||
|
||||
if ! has_cmd brew; then
|
||||
if [ -x "/opt/homebrew/bin/brew" ]; then
|
||||
eval "$(/opt/homebrew/bin/brew shellenv)"
|
||||
elif [ -x "/usr/local/bin/brew" ]; then
|
||||
eval "$(/usr/local/bin/brew shellenv)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if has_cmd brew; then
|
||||
echo -e " ${CYAN}Detected Homebrew ($(brew --version | head -n1)). Installing missing components...${NC}"
|
||||
if [ "${need_adb}" = true ]; then
|
||||
echo -e " 📦 Installing ${BOLD}android-platform-tools${NC}..."
|
||||
brew install --cask android-platform-tools || brew install android-platform-tools || true
|
||||
fi
|
||||
if [ "${need_ffmpeg}" = true ]; then
|
||||
echo -e " 📦 Installing ${BOLD}ffmpeg${NC}..."
|
||||
brew install ffmpeg || true
|
||||
fi
|
||||
if [ "${need_scrcpy}" = true ]; then
|
||||
echo -e " 📦 Installing ${BOLD}scrcpy${NC}..."
|
||||
brew install scrcpy || true
|
||||
fi
|
||||
else
|
||||
echo -e " ${YELLOW}⚠ Homebrew not found. Please install Homebrew or install tools manually:${NC}"
|
||||
echo -e " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
|
||||
echo -e " brew install --cask android-platform-tools && brew install ffmpeg scrcpy"
|
||||
fi
|
||||
|
||||
elif [ "${OS_TYPE}" = "Linux" ]; then
|
||||
SUDO_CMD=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
if has_cmd sudo; then
|
||||
SUDO_CMD="sudo"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠ Root/sudo privileges not detected. Please install system tools with your package manager.${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if has_cmd apt-get; then
|
||||
echo -e " ${CYAN}Detected Debian/Ubuntu (apt-get). Synchronizing and installing...${NC}"
|
||||
PKGS=()
|
||||
[ "${need_adb}" = true ] && PKGS+=("adb")
|
||||
[ "${need_ffmpeg}" = true ] && PKGS+=("ffmpeg")
|
||||
[ "${need_scrcpy}" = true ] && PKGS+=("scrcpy")
|
||||
PKGS+=("curl" "git")
|
||||
|
||||
if [ -n "${SUDO_CMD}" ] || [ "$(id -u)" -eq 0 ]; then
|
||||
${SUDO_CMD} apt-get update -qq || true
|
||||
${SUDO_CMD} apt-get install -y "${PKGS[@]}" || true
|
||||
fi
|
||||
elif has_cmd dnf; then
|
||||
echo -e " ${CYAN}Detected Fedora/RHEL (dnf). Installing packages...${NC}"
|
||||
PKGS=()
|
||||
[ "${need_adb}" = true ] && PKGS+=("android-tools")
|
||||
[ "${need_ffmpeg}" = true ] && PKGS+=("ffmpeg")
|
||||
[ "${need_scrcpy}" = true ] && PKGS+=("scrcpy")
|
||||
PKGS+=("curl" "git")
|
||||
|
||||
if [ -n "${SUDO_CMD}" ] || [ "$(id -u)" -eq 0 ]; then
|
||||
${SUDO_CMD} dnf install -y "${PKGS[@]}" || true
|
||||
fi
|
||||
elif has_cmd pacman; then
|
||||
echo -e " ${CYAN}Detected Arch Linux (pacman). Installing packages...${NC}"
|
||||
PKGS=()
|
||||
[ "${need_adb}" = true ] && PKGS+=("android-tools")
|
||||
[ "${need_ffmpeg}" = true ] && PKGS+=("ffmpeg")
|
||||
[ "${need_scrcpy}" = true ] && PKGS+=("scrcpy")
|
||||
PKGS+=("curl" "git")
|
||||
|
||||
if [ -n "${SUDO_CMD}" ] || [ "$(id -u)" -eq 0 ]; then
|
||||
${SUDO_CMD} pacman -S --noconfirm "${PKGS[@]}" || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to ensure uv is installed and ready
|
||||
ensure_uv() {
|
||||
echo -e "\n${BOLD}3. Checking Python Package Manager (uv)...${NC}"
|
||||
if ! has_cmd uv; then
|
||||
echo -e " ${YELLOW}uv not found. Installing Astral uv (ultra-fast Python package manager)...${NC}"
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
|
||||
fi
|
||||
|
||||
# Explicitly persist PATH to shell configuration files if not present
|
||||
local export_line='export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"'
|
||||
for rc in "${HOME}/.bashrc" "${HOME}/.zshrc" "${HOME}/.profile"; do
|
||||
if [ -f "${rc}" ]; then
|
||||
if ! grep -qs 'HOME/\.local/bin' "${rc}"; then
|
||||
echo -e "\n# Added by Artemis installer\n${export_line}" >> "${rc}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# If /usr/local/bin is writable or sudo is available, try to create a symlink to make it available immediately
|
||||
local uv_bin=""
|
||||
if [ -x "${HOME}/.local/bin/uv" ]; then
|
||||
uv_bin="${HOME}/.local/bin/uv"
|
||||
elif command -v uv >/dev/null 2>&1; then
|
||||
uv_bin="$(command -v uv)"
|
||||
fi
|
||||
|
||||
if [ -n "${uv_bin}" ]; then
|
||||
if [ -w "/usr/local/bin" ]; then
|
||||
ln -sf "${uv_bin}" /usr/local/bin/uv 2>/dev/null || true
|
||||
[ -x "${HOME}/.local/bin/uvx" ] && ln -sf "${HOME}/.local/bin/uvx" /usr/local/bin/uvx 2>/dev/null || true
|
||||
elif has_cmd sudo; then
|
||||
sudo -n ln -sf "${uv_bin}" /usr/local/bin/uv 2>/dev/null || true
|
||||
[ -x "${HOME}/.local/bin/uvx" ] && sudo -n ln -sf "${HOME}/.local/bin/uvx" /usr/local/bin/uvx 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
if has_cmd uv; then
|
||||
echo -e " ${GREEN}✓ uv is ready ($(uv --version))${NC}"
|
||||
else
|
||||
echo -e " ${RED}✗ Failed to find uv in PATH. Please add ~/.local/bin to your PATH.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to setup Python runtime and sync dependencies
|
||||
setup_python_env() {
|
||||
echo -e "\n${BOLD}4. Configuring Python Runtime & Syncing Dependencies...${NC}"
|
||||
echo -e " ${BLUE}📦 Running uv sync (auto-provisioning Python >=3.12 & dependencies)...${NC}"
|
||||
uv sync
|
||||
echo -e " ${GREEN}✓ Python runtime and dependencies synced successfully.${NC}"
|
||||
}
|
||||
|
||||
# Function to initialize .env configuration
|
||||
setup_env_file() {
|
||||
echo -e "\n${BOLD}5. Checking Environment Configuration (.env)...${NC}"
|
||||
if [ ! -f ".env" ]; then
|
||||
if [ -f ".env.example" ]; then
|
||||
cp .env.example .env
|
||||
echo -e " ${GREEN}✓ Created .env template from .env.example.${NC}"
|
||||
else
|
||||
touch .env
|
||||
echo -e " ${GREEN}✓ Created empty .env file.${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓ .env configuration file exists.${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to display system readiness report
|
||||
verify_readiness() {
|
||||
echo -e "\n${BOLD}6. Toolchain Readiness Summary:${NC}"
|
||||
|
||||
local tools=("adb" "ffmpeg" "scrcpy" "uv" "python3")
|
||||
for t in "${tools[@]}"; do
|
||||
if has_cmd "$t"; then
|
||||
local loc
|
||||
loc="$(command -v "$t")"
|
||||
echo -e " ${GREEN}✔ ${t}${NC} -> ${DIM}${loc}${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}○ ${t}${NC} -> ${YELLOW}Not found in PATH (Fallbacks active)${NC}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Run setup workflow
|
||||
install_system_packages
|
||||
ensure_uv
|
||||
setup_python_env
|
||||
setup_env_file
|
||||
verify_readiness
|
||||
|
||||
echo -e "\n${BOLD}${CYAN}======================================================${NC}"
|
||||
echo -e "${BOLD}${GREEN} ✨ Artemis Environment Ready! ${NC}"
|
||||
echo -e "${BOLD}${CYAN}======================================================${NC}"
|
||||
|
||||
if [ "${AUTO_LAUNCH}" = true ]; then
|
||||
echo -e "\n${GREEN}🚀 Auto-launching Showcase UI...${NC}"
|
||||
exec uv run artemis ui --open
|
||||
else
|
||||
echo -e "To launch the unified UI & live device onboarding:"
|
||||
echo -e " 👉 ${BOLD}${CYAN}./start.sh${NC} (Recommended - automatically sets up environment)"
|
||||
echo -e " 👉 ${BOLD}source ~/.bashrc && uv run artemis ui${NC} (or open a new terminal tab)"
|
||||
echo ""
|
||||
fi
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
"""Artemis UI Quick Launcher (run_ui.py)
|
||||
|
||||
Fastest and simplest way to launch the Artemis Web UI across all platforms:
|
||||
- Automatically detects if the UI is already running and opens the browser directly.
|
||||
- Uses uv or existing virtualenv seamlessly.
|
||||
- Zero-configuration one-click start.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
import webbrowser
|
||||
|
||||
ROOT_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def is_port_in_use(port: int, host: str = "127.0.0.1") -> bool:
|
||||
"""Check whether a TCP port is in use."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(0.5)
|
||||
return s.connect_ex((host, port)) == 0
|
||||
|
||||
|
||||
def is_artemis_ui_responsive(url: str, timeout: float = 1.0) -> bool:
|
||||
"""Check if Artemis UI server is responding on given URL."""
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "Artemis-QuickLauncher/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return resp.status in (200, 304)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def find_python_executable() -> str:
|
||||
"""Find the best Python executable (.venv or system)."""
|
||||
# 1. Check local .venv
|
||||
venv_py = (
|
||||
ROOT_DIR
|
||||
/ ".venv"
|
||||
/ ("Scripts" if os.name == "nt" else "bin")
|
||||
/ ("python.exe" if os.name == "nt" else "python")
|
||||
)
|
||||
if venv_py.is_file() and os.access(venv_py, os.X_OK if os.name != "nt" else os.F_OK):
|
||||
return str(venv_py)
|
||||
|
||||
# 2. Return current sys.executable
|
||||
return sys.executable
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="✨ One-click launcher for Artemis Mobile Agent UI",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument("-p", "--port", type=int, default=8000, help="Port to run UI on")
|
||||
parser.add_argument("-H", "--host", type=str, default="0.0.0.0", help="Host address to bind")
|
||||
parser.add_argument("--no-open", action="store_true", help="Do not automatically open browser")
|
||||
parser.add_argument(
|
||||
"--reload", action="store_true", help="Enable uvicorn auto-reload for development"
|
||||
)
|
||||
args, unknown = parser.parse_known_args()
|
||||
|
||||
ui_url = f"http://localhost:{args.port}"
|
||||
admin_url = f"http://localhost:{args.port}/admin"
|
||||
|
||||
print("\033[1;36m" + "=" * 56 + "\033[0m")
|
||||
print("\033[1;36m ✨ Artemis Autonomous Mobile Agent UI \033[0m")
|
||||
print("\033[1;36m" + "=" * 56 + "\033[0m\n")
|
||||
|
||||
# Check if server is already running
|
||||
if is_port_in_use(args.port):
|
||||
if is_artemis_ui_responsive(ui_url):
|
||||
print(f"\033[1;32m✓ Artemis UI is already running at:\033[0m \033[1;36m{ui_url}\033[0m")
|
||||
print(f"\033[1;35m🛠️ Admin Console:\033[0m \033[1;36m{admin_url}\033[0m\n")
|
||||
if not args.no_open:
|
||||
print("🌐 Opening browser...")
|
||||
webbrowser.open(ui_url)
|
||||
return
|
||||
else:
|
||||
print(f"\033[1;33m⚠️ Port {args.port} is in use by another process.\033[0m")
|
||||
print("Attempting to connect or start on specified port...")
|
||||
|
||||
# Choose execution engine: uv run artemis ui > direct python
|
||||
has_uv = (
|
||||
subprocess.run(
|
||||
["which", "uv"] if os.name != "nt" else ["where", "uv"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
cmd = []
|
||||
if has_uv and (ROOT_DIR / "pyproject.toml").exists():
|
||||
cmd = ["uv", "run", "artemis", "ui", "--port", str(args.port), "--host", args.host]
|
||||
if args.no_open:
|
||||
cmd.append("--no-open")
|
||||
if args.reload:
|
||||
cmd.append("--reload")
|
||||
else:
|
||||
python_bin = find_python_executable()
|
||||
# Set PYTHONPATH so apps and artemis are importable
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = (
|
||||
f"{ROOT_DIR}{os.pathsep}{ROOT_DIR / 'apps'}{os.pathsep}{env.get('PYTHONPATH', '')}"
|
||||
)
|
||||
cmd = [python_bin, "-m", "artemis", "ui", "--port", str(args.port), "--host", args.host]
|
||||
if args.no_open:
|
||||
cmd.append("--no-open")
|
||||
if args.reload:
|
||||
cmd.append("--reload")
|
||||
|
||||
try:
|
||||
# Run the server
|
||||
subprocess.run(cmd, cwd=str(ROOT_DIR))
|
||||
except KeyboardInterrupt:
|
||||
print("\n\033[1;33m🛑 Artemis UI server stopped.\033[0m")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,106 @@
|
||||
# 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
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$RootDir = Split-Path -Parent $ScriptDir
|
||||
Set-Location $RootDir
|
||||
|
||||
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\Android\Sdk\platform-tools",
|
||||
"$env:ProgramFiles\Android\platform-tools",
|
||||
"C:\ProgramData\chocolatey\bin",
|
||||
"$env:USERPROFILE\scoop\shims",
|
||||
"$env:USERPROFILE\.local\bin",
|
||||
"$env:USERPROFILE\.cargo\bin"
|
||||
)
|
||||
$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 ";"
|
||||
}
|
||||
|
||||
function Test-CommandExists {
|
||||
param([string]$Command)
|
||||
$res = Get-Command $Command -ErrorAction SilentlyContinue
|
||||
return ($null -ne $res)
|
||||
}
|
||||
|
||||
# 1. Refresh PATH with standard toolchain paths
|
||||
Update-EnvironmentPath
|
||||
|
||||
# 2. Check or install uv
|
||||
if (-not (Test-CommandExists "uv")) {
|
||||
Write-Host "⚡ uv not found. Installing Astral uv..." -ForegroundColor Yellow
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
Update-EnvironmentPath
|
||||
}
|
||||
|
||||
# 3. Check and try installing missing system toolchains (ADB, FFmpeg, scrcpy)
|
||||
$missingCore = @()
|
||||
if (-not (Test-CommandExists "adb")) { $missingCore += "adb" }
|
||||
if (-not (Test-CommandExists "ffmpeg")) { $missingCore += "ffmpeg" }
|
||||
if (-not (Test-CommandExists "scrcpy")) { $missingCore += "scrcpy" }
|
||||
|
||||
if ($missingCore.Count -gt 0) {
|
||||
if (Test-CommandExists "winget") {
|
||||
Write-Host "⚡ 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
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Check or initialize .env
|
||||
if (-not (Test-Path ".env")) {
|
||||
if (Test-Path ".env.example") {
|
||||
Copy-Item ".env.example" ".env"
|
||||
Write-Host " ✔ Initialized .env configuration file." -ForegroundColor Green
|
||||
} else {
|
||||
New-Item -ItemType File -Path ".env" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# 5. Synchronize dependencies
|
||||
Write-Host " 📦 Synchronizing Python runtime and project dependencies..." -ForegroundColor Cyan
|
||||
uv sync --quiet
|
||||
|
||||
# 6. Launch unified Showcase UI & open browser
|
||||
Write-Host " 🚀 Launching Artemis Showcase UI & Admin Console..." -ForegroundColor Green
|
||||
if ($NoOpen) {
|
||||
uv run artemis ui --port $Port --no-open
|
||||
} else {
|
||||
uv run artemis ui --port $Port --open
|
||||
}
|
||||
Reference in new issue
Block a user