Files
Artemis-Eapil/artemis/config/paths.py
T
somew1nd 206a9ce921 feat: initial release of ARTEMIS Android Agent & Automation Platform
ARTEMIS is an autonomous multimodal Android agent and mobile UI automation
platform engineered for physical devices and emulators. It combines state-of-the-art
multimodal Vision-Language Models (VLMs) with hierarchical closed-loop planning,
resilient self-healing, and deep Android subsystem integration.

Key Features & Architectural Highlights:
- Dual-Engine Orchestration:
  * Pro Engine: Multi-agent closed-loop (Planner -> Operator -> Validator/Failure Analyzer -> Summarizer) powered by LangGraph.
  * Flash Engine: Ultra-fast reactive loop for deterministic, low-latency UI execution (<35 steps).
- Multimodal Perception & Grounding:
  * Hybrid visual screenshot grounding, real-time OCR extraction, and XML accessibility hierarchy parsing.
  * Dynamic-First, Coordinate-Fallback locating strategy for maximum resilience against UI drift.
- Autonomous Self-Healing & Diagnostics:
  * Failure analyzer and safety net for popup dismissal, crash recovery, and auto-retry.
  * Diagnostic sub-agents for log analysis, video recording inspection, and object detection.
- Unified CLI & Python SDK:
  * Rich CLI with commands: init, doctor, run, trace, batch, bench, server, and mcp.
  * Fluent Python SDK with Pydantic structured output models and package locking.
- Multi-Provider LLM Architecture:
  * Support for Google Gemini, OpenAI, Anthropic Claude, Vertex AI, OpenRouter, xAI Grok, and local endpoints (Ollama/vLLM).
  * Centralized configuration in config/artemis.jsonc.
- Native MCP & Observability:
  * Model Context Protocol (MCP) server endpoints for AI IDEs.
  * Full trace recording with before/after screenshots, action overlays, and execution replays.

Change-Id: I430b93ee2b76b54cc5a3c6eeccd15d2cf29830a1
2026-08-13 11:46:47 -07:00

171 lines
5.5 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.
"""Centralized workspace, configuration, temporary files, and directory management."""
import os
from pathlib import Path
import sys
from artemis.config.constants import (
DATA_ENGINE_DB_FILENAME,
ENV_ANTIGRAVITY_APP_DIR,
ENV_ARTEMIS_APP_DIR,
ENV_ARTEMIS_TRACES_DIR,
ENV_ARTEMIS_USE_USER_DIR,
IPC_PORT_FILENAME,
LS_ADDRESS_FILENAME,
)
# Project root directory (3 levels up from artemis/config/paths.py)
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
CONFIG_DIR = ROOT_DIR / "config"
def is_frozen_bundle() -> bool:
"""Check if running inside a PyInstaller / Nuitka standalone compiled binary."""
return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
def get_app_dir() -> Path:
"""Returns the central application directory for ARTEMIS configurations, DBs, and traces.
Order of precedence:
1. ARTEMIS_APP_DIR or ANTIGRAVITY_APP_DIR environment variable.
2. Legacy ~/.gemini/jetski directory if it exists.
3. Default ~/.artemis directory.
"""
env_dir = os.getenv(ENV_ARTEMIS_APP_DIR) or os.getenv(ENV_ANTIGRAVITY_APP_DIR)
if env_dir:
path = Path(env_dir)
path.mkdir(parents=True, exist_ok=True)
return path
legacy_dir = Path(os.path.expanduser("~/.gemini/jetski"))
if legacy_dir.exists():
return legacy_dir
default_dir = Path(os.path.expanduser("~/.artemis"))
default_dir.mkdir(parents=True, exist_ok=True)
return default_dir
def get_default_traces_path() -> Path:
"""Returns default traces directory.
Uses user app dir when running as a frozen binary or when app dir env is set.
"""
env_traces = os.getenv(ENV_ARTEMIS_TRACES_DIR)
if env_traces:
traces_dir = Path(env_traces)
elif (
is_frozen_bundle()
or os.getenv(ENV_ARTEMIS_USE_USER_DIR) == "true"
or os.getenv(ENV_ARTEMIS_APP_DIR)
or os.getenv(ENV_ANTIGRAVITY_APP_DIR)
):
traces_dir = get_app_dir() / "traces"
else:
traces_dir = ROOT_DIR / "traces"
traces_dir.mkdir(parents=True, exist_ok=True)
return traces_dir
def get_traces_dir() -> Path:
"""Alias for get_default_traces_path."""
return get_default_traces_path()
def get_temp_dir(subfolder: str | None = None) -> Path:
"""Returns a unified temporary directory for ARTEMIS runtime operations (e.g., screenshots, recordings).
Args:
subfolder: Optional subfolder name under the temp directory.
Returns:
Path object pointing to the initialized temporary directory.
"""
base_temp = get_app_dir() / "temp"
target_dir = base_temp / subfolder if subfolder else base_temp
target_dir.mkdir(parents=True, exist_ok=True)
return target_dir
def get_data_engine_db_path() -> Path:
"""Returns the unified SQLite database path for ARTEMIS DataEngine."""
return get_default_traces_path() / DATA_ENGINE_DB_FILENAME
def get_ipc_port_file() -> Path:
"""Returns the location of the IPC port synchronization file."""
app_dir_file = get_app_dir() / IPC_PORT_FILENAME
if app_dir_file.exists():
return app_dir_file
return ROOT_DIR / IPC_PORT_FILENAME
def get_ls_address_file() -> Path:
"""Returns the location of the Language Server address synchronization file."""
app_dir_file = get_app_dir() / LS_ADDRESS_FILENAME
if app_dir_file.exists():
return app_dir_file
return ROOT_DIR / LS_ADDRESS_FILENAME
def get_config_path(filename: str, default_bundled_path: Path | None = None) -> Path:
"""Resolve a configuration file path across env vars, config dir, project dir, app dir, and bundles.
Resolution Order:
1. Environment variable override: `ARTEMIS_<FILENAME_UPPER>`
2. Central `config/` directory: `<ROOT_DIR>/config/<filename>`
3. Project root directory: `<ROOT_DIR>/<filename>`
4. Custom default bundled path (if provided and exists)
5. User application directory: `<APP_DIR>/<filename>`
"""
env_var = f"ARTEMIS_{filename.upper().replace('.', '_').replace('-', '_')}"
env_path_str = os.getenv(env_var)
if env_path_str:
env_path = Path(env_path_str)
if env_path.exists():
return env_path
# Check config/ directory
config_dir_path = CONFIG_DIR / filename
if config_dir_path.exists():
return config_dir_path
# Check project root directory
project_path = ROOT_DIR / filename
if project_path.exists():
return project_path
# Check bundled fallback
if default_bundled_path and default_bundled_path.exists():
return default_bundled_path
# Check user app directory
app_dir_path = get_app_dir() / filename
if app_dir_path.exists():
return app_dir_path
raise FileNotFoundError(
f"Configuration file '{filename}' not found in config dir ({config_dir_path}), "
f"project root ({project_path}), or app dir ({app_dir_path})."
)
GLOBAL_APP_DIR = get_app_dir()
GLOBAL_JETSKI_DIR = GLOBAL_APP_DIR # Backward-compatibility alias