Files
Artemis-Eapil/artemis/interfaces/cli/commands/doctor.py
T
somew1nd 9a5aaf99b7 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
2026-08-18 13:59:27 -07:00

279 lines
9.2 KiB
Python

# 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.
"""System, environment, and device diagnostics (artemis doctor)."""
import platform as sys_platform
import subprocess
import sys
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from artemis.config import (
parse_llm_config,
settings,
)
from artemis.platform import OSType, platform
from artemis.toolchain import toolchain
from artemis.utils.ocr_api import is_ocr_configured
def doctor_command() -> None:
"""Run diagnostics to inspect system dependencies, device connectivity, and configuration."""
console = Console()
console.print()
os_name = sys_platform.system()
arch_name = sys_platform.machine()
console.print(
Panel(
f"[bold cyan]☕ Artemis System & Environment Doctor[/bold cyan]\n"
f"[dim]Platform: {os_name} ({arch_name}) | PAL: {platform.os_type.value}[/dim]",
expand=False,
)
)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Component", style="dim", width=22)
table.add_column("Status", width=12)
table.add_column("Details & Recommendations")
all_passed = True
# 1. Python Version
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
if sys.version_info >= (3, 12):
table.add_row(
"Python Runtime",
"[bold green]✔ OK[/bold green]",
f"Python {py_ver} ({arch_name}, 64-bit)",
)
else:
all_passed = False
table.add_row(
"Python Runtime",
"[bold red]✖ Warning[/bold red]",
f"Python {py_ver} detected. Python >= 3.12 is strongly recommended.",
)
# 2. ADB Installation & Server
adb_path = toolchain.resolve("adb")
if adb_path:
try:
res = subprocess.run(
[adb_path, "version"],
capture_output=True,
text=True,
timeout=5,
encoding="utf-8",
errors="replace",
)
adb_info = res.stdout.splitlines()[0] if res.stdout else "Installed"
table.add_row(
"Android ADB", "[bold green]✔ OK[/bold green]", f"{adb_path} ({adb_info})"
)
except Exception:
table.add_row("Android ADB", "[bold green]✔ OK[/bold green]", f"{adb_path}")
else:
all_passed = False
hint = (
"Run: winget install Google.PlatformTools"
if platform.os_type == OSType.WINDOWS
else "Run: brew install android-platform-tools"
if platform.os_type == OSType.MACOS
else "Run: sudo apt-get install -y adb"
)
table.add_row(
"Android ADB",
"[bold red]✖ Missing[/bold red]",
f"adb not found in PATH or Android SDK. {hint}",
)
# 3. Connected Android Devices
devices_info: list[tuple[str, str, str]] = []
if adb_path:
try:
res = subprocess.run(
[adb_path, "devices", "-l"],
capture_output=True,
text=True,
timeout=5,
encoding="utf-8",
errors="replace",
)
lines = [line_str.strip() for line_str in res.stdout.splitlines() if line_str.strip()]
for line in lines[1:]: # skip 'List of devices attached'
parts = line.split()
if len(parts) >= 2:
serial = parts[0]
state = parts[1]
extra = " ".join(parts[2:])
devices_info.append((serial, state, extra))
except Exception:
pass
if devices_info:
active_devices = [d for d in devices_info if d[1] == "device"]
if active_devices:
summary = "\n".join(
[
f"• [bold cyan]{d[0]}[/bold cyan] ({d[2] or 'Authorized'})"
for d in active_devices
]
)
table.add_row("Android Devices", "[bold green]✔ Connected[/bold green]", summary)
else:
all_passed = False
table.add_row(
"Android Devices",
"[bold yellow]⚠ Unauthorized[/bold yellow]",
"Device detected but unauthorized. Check phone screen for USB Debugging prompt.",
)
else:
table.add_row(
"Android Devices",
"[bold yellow]⚠ None Found[/bold yellow]",
"No Android device or emulator detected. Connect phone via USB or start emulator.",
)
# 4. LLM & Vision OCR Key Configuration
gemini_key = settings.get_api_key("google")
ocr_key = settings.get_api_key("ocr")
openai_key = settings.get_api_key("openai")
claude_key = settings.get_api_key("anthropic")
openrouter_key = settings.get_api_key("openrouter")
configured_keys = []
if gemini_key:
val = gemini_key.get_secret_value()
masked = f"{val[:6]}...{val[-4:]}" if len(val) > 10 else "***"
configured_keys.append(f"Gemini LLM ({masked})")
if openai_key:
configured_keys.append("OpenAI")
if claude_key:
configured_keys.append("Anthropic")
if openrouter_key:
configured_keys.append("OpenRouter")
if configured_keys:
table.add_row(
"LLM Providers", "[bold green]✔ Configured[/bold green]", ", ".join(configured_keys)
)
else:
all_passed = False
table.add_row(
"LLM Providers",
"[bold red]✖ Missing[/bold red]",
"No LLM key found in .env. Run [bold cyan]artemis init[/bold cyan] to configure.",
)
# 5. Vision OCR Key Check (Optional)
if is_ocr_configured() and ocr_key:
val = ocr_key.get_secret_value()
masked = f"{val[:6]}...{val[-4:]}" if len(val) > 10 else "***"
table.add_row(
"Vision OCR",
"[bold green]✔ Configured[/bold green]",
f"Google Cloud Vision API ({masked})",
)
else:
table.add_row(
"Vision OCR",
"[bold cyan]○ Optional[/bold cyan]",
"OCR_API_KEY is not set. Perception operates in pure XML layout mode.",
)
# 6. Configuration File Health
try:
cfg = parse_llm_config()
planner_model = cfg.planner.model
table.add_row(
"Configuration",
"[bold green]✔ Valid[/bold green]",
f"config/artemis.jsonc parsed (Planner: {planner_model})",
)
except Exception as e:
all_passed = False
table.add_row(
"Configuration",
"[bold red]✖ Error[/bold red]",
f"Failed to parse config/artemis.jsonc: {e}",
)
# 7. Video Recording Tools (FFmpeg & scrcpy)
ffmpeg_path = toolchain.resolve("ffmpeg")
scrcpy_path = toolchain.resolve("scrcpy")
if ffmpeg_path:
table.add_row(
"FFmpeg Tools", "[bold green]✔ Installed[/bold green]", f"{ffmpeg_path} (Video ready)"
)
else:
hint = (
"winget install Gyan.FFmpeg"
if platform.os_type == OSType.WINDOWS
else "brew install ffmpeg"
if platform.os_type == OSType.MACOS
else "sudo apt-get install -y ffmpeg"
)
table.add_row(
"FFmpeg Tools",
"[dim]⚪ Optional[/dim]",
f"Not found. ({hint})",
)
if scrcpy_path:
table.add_row(
"Screen Recorder",
"[bold green]✔ Installed[/bold green]",
f"{scrcpy_path} (Live stream ready)",
)
else:
hint = (
"winget install Genymobile.scrcpy"
if platform.os_type == OSType.WINDOWS
else "brew install scrcpy"
if platform.os_type == OSType.MACOS
else "sudo apt-get install -y scrcpy"
)
table.add_row(
"Screen Recorder",
"[dim]⚪ Optional[/dim]",
f"scrcpy not found. ({hint})",
)
console.print(table)
console.print()
if all_passed:
console.print(
Panel(
"[bold green]🎉 All system checks passed![/bold green]\n\n"
"Run your first automation task now:\n"
' [bold cyan]artemis run "Open Settings and check Battery level"[/bold cyan]',
title="Status: Ready",
expand=False,
)
)
else:
console.print(
Panel(
"[bold yellow]💡 Tip:[/bold yellow] Run [bold cyan]artemis init[/bold cyan] to quickly set up your API key and device.",
title="Action Required",
expand=False,
)
)
console.print()