diff --git a/.gitignore b/.gitignore
index ec4d3c2..6ce825b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,6 +81,7 @@ data_engine.db
data_engine.db-journal
.ipc_port
.ls_address
+.jetski_ls_address
# Environment & Secrets
.env
diff --git a/Makefile b/Makefile
index 8ccba9a..5e366b3 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-.PHONY: help test install install-deps setup start ui doctor clean precommit-install precommit lint format typecheck
+.PHONY: help test install install-deps setup start ui build-ui doctor clean precommit-install precommit lint format typecheck
help: ## Show this help message
@echo 'Usage: make [target]'
@@ -26,6 +26,10 @@ start: ## One-click start Artemis Showcase UI and auto-open browser
ui: ## Launch the unified Showcase UI & Admin Console in browser
@uv run artemis ui --open
+build-ui: ## Build the Showcase UI Angular frontend
+ @echo "🎨 Building Showcase UI..."
+ @cd apps/showcase_ui && npm install && npm run build
+
doctor: ## Run system, device, and toolchain diagnostics
@uv run artemis doctor
@@ -41,9 +45,10 @@ install-deps: ## One-click install all system dependencies (ADB, FFmpeg, scrcpy,
@echo "⚡ Running one-click dependency installer..."
@bash scripts/install_deps.sh
-setup: ## Setup project (install dependencies + pre-commit hooks)
+setup: ## Setup project (install dependencies + pre-commit hooks + build UI)
@echo "🚀 Setting up project..."
@$(MAKE) install
+ @$(MAKE) build-ui
@$(MAKE) precommit-install
@echo ""
@echo "✅ Setup complete! You're ready to start developing."
diff --git a/README.md b/README.md
index 53257ea..e9fd55c 100644
--- a/README.md
+++ b/README.md
@@ -105,28 +105,40 @@ start.bat
ARTEMIS includes a native **Model Context Protocol (MCP)** server. Connect your real phone directly into AI IDEs:
-### 1. Generate MCP Config
+### 1. One-Click Auto Install (Recommended)
-Run the built-in generator to produce ready-to-use JSON configuration:
+Run the built-in installer to automatically configure or update your AI IDE (automatically resolves your virtual environment Python and repository path):
```bash
-artemis mcp --generate-config antigravity
-# Or generate configs for all IDEs:
-artemis mcp --generate-config all
+# Auto-install for Antigravity / Jetski:
+artemis mcp --install antigravity
+
+# Or install for all detected AI IDEs (Antigravity, Cursor, Claude Desktop/Code, OpenClaw):
+artemis mcp --install all
```
-### 2. Copy Config to Your IDE
+> 💡 **Tip**: You can also configure MCP interactively during first-time setup via `artemis init`.
-* **Antigravity** (MCP config file or Settings ➔ MCP Servers):
+### 2. Manual Configuration (Optional)
+
+If you prefer to configure manually, run `artemis mcp --generate-config antigravity` (or `all`) to output the exact JSON snippet, or paste the following into your IDE configuration (replace `/path/to/artemis` with your actual repo path and point `command` to your `.venv` Python executable):
+
+* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
```json
{
"mcpServers": {
"artemis": {
- "command": "python",
+ "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis",
"env": {
"PYTHONUNBUFFERED": "1"
+ },
+ "tools": {
+ "mobile_run_task": { "eager": true },
+ "mobile_manage_task": { "eager": true },
+ "mobile_get_device_state": { "eager": true },
+ "mobile_inspect_trace": { "eager": true }
}
}
}
@@ -138,7 +150,7 @@ artemis mcp --generate-config all
{
"mcpServers": {
"artemis": {
- "command": "python",
+ "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis"
}
diff --git a/README_CN.md b/README_CN.md
index 4aa764e..28afb73 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -107,28 +107,40 @@ start.bat
ARTEMIS 内置原生 **Model Context Protocol (MCP)** 服务。只需将以下配置加入你的 IDE 配置文件中,即可在编写代码时直接驱动真机:
-### 1. 生成或查看配置
+### 1. 一键自动安装到 IDE(推荐)
-运行内置命令一键获取当前环境的完整配置 JSON:
+运行内置安装命令,即可自动配置或更新你的 AI IDE(会自动解析并填写虚拟环境 Python 和项目的绝对路径):
```bash
-artemis mcp --generate-config antigravity
-# 或生成所有 IDE 配置:
-artemis mcp --generate-config all
+# 一键安装到 Antigravity / Jetski:
+artemis mcp --install antigravity
+
+# 或一键安装到所有检测到的 AI IDE(Antigravity、Cursor、Claude Desktop/Code、OpenClaw):
+artemis mcp --install all
```
-### 2. 复制配置到 IDE
+> 💡 **提示**:你也可以在首次运行 `artemis init` 配置向导时,交互式完成 IDE 的 MCP 自动挂载。
-* **Antigravity** (MCP 配置文件或 设置 ➔ MCP Servers):
+### 2. 手动配置(可选)
+
+如果你习惯手动复制配置,可运行 `artemis mcp --generate-config antigravity`(或 `all`)获取自动计算好路径的 JSON 代码段,也可以参考以下模板将 `command` 填写为项目下 `.venv` 虚拟环境中的 Python 绝对路径,并将 `/path/to/artemis` 替换为项目实际路径:
+
+* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
```json
{
"mcpServers": {
"artemis": {
- "command": "python",
+ "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis",
"env": {
"PYTHONUNBUFFERED": "1"
+ },
+ "tools": {
+ "mobile_run_task": { "eager": true },
+ "mobile_manage_task": { "eager": true },
+ "mobile_get_device_state": { "eager": true },
+ "mobile_inspect_trace": { "eager": true }
}
}
}
@@ -140,7 +152,7 @@ artemis mcp --generate-config all
{
"mcpServers": {
"artemis": {
- "command": "python",
+ "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis"
}
diff --git a/apps/admin_console/server.py b/apps/admin_console/server.py
index 43c7fe2..cd3ace9 100644
--- a/apps/admin_console/server.py
+++ b/apps/admin_console/server.py
@@ -136,50 +136,134 @@ except ImportError:
# ------------------------------------------------------------------------------
# Showcase UI (Angular 19) Unified Single-Port SPA Static Hosting
# ------------------------------------------------------------------------------
-_showcase_dist = _workspace_root / "apps" / "showcase_ui" / "dist" / "frontend" / "browser"
-if not _showcase_dist.exists():
- _showcase_dist = _workspace_root / "apps" / "showcase_ui" / "dist"
+def _get_showcase_dist() -> Path:
+ """Return the Showcase UI Angular build directory."""
+ base_dist = _workspace_root / "apps" / "showcase_ui" / "dist"
+ candidates = [
+ base_dist / "frontend" / "browser",
+ base_dist / "browser",
+ base_dist / "frontend",
+ base_dist,
+ ]
+ for candidate in candidates:
+ if candidate.is_dir() and (candidate / "index.html").exists():
+ return candidate
+ return base_dist / "frontend" / "browser"
-if _showcase_dist.is_dir() and (_showcase_dist / "index.html").exists():
- @app.get("/{full_path:path}", include_in_schema=False)
- async def serve_showcase_spa(full_path: str):
- # Do not intercept API, media, or replay paths
- if (
- full_path.startswith("api/")
- or full_path.startswith("images/")
- or full_path.startswith("videos/")
- or full_path.startswith("local_file")
- or full_path == "docs"
- or full_path == "openapi.json"
- or full_path.startswith("redoc")
- ):
- from fastapi import HTTPException
+@app.get("/", include_in_schema=False)
+async def serve_showcase_root():
+ """Explicitly serve the Showcase UI at the root path by default."""
+ return await serve_showcase_spa("")
- raise HTTPException(status_code=404, detail="Endpoint not found")
- # Admin / Debug Console routes
- if full_path in ("admin", "debug"):
- admin_index = _admin_console_dir / "index.html"
- if admin_index.exists():
- return HTMLResponse(admin_index.read_text(encoding="utf-8"))
+@app.get("/{full_path:path}", include_in_schema=False)
+async def serve_showcase_spa(full_path: str):
+ # Do not intercept API, media, or replay paths
+ if (
+ full_path.startswith("api/")
+ or full_path.startswith("images/")
+ or full_path.startswith("videos/")
+ or full_path.startswith("local_file")
+ or full_path == "docs"
+ or full_path == "openapi.json"
+ or full_path.startswith("redoc")
+ ):
+ from fastapi import HTTPException
- # Exact static file match (js, css, images, fonts, favicon, logo)
- target_file = _showcase_dist / full_path
- if full_path and target_file.is_file():
- return FileResponse(target_file)
+ raise HTTPException(status_code=404, detail="Endpoint not found")
- # Default fallback to Angular SPA index.html
- index_file = _showcase_dist / "index.html"
- return HTMLResponse(index_file.read_text(encoding="utf-8"))
-else:
+ clean_path = full_path.strip("/")
- @app.get("/", response_class=HTMLResponse, include_in_schema=False)
- async def fallback_root():
+ # Admin / Debug Console routes
+ if (
+ clean_path in ("admin", "debug")
+ or clean_path.startswith("admin/")
+ or clean_path.startswith("debug/")
+ ):
admin_index = _admin_console_dir / "index.html"
if admin_index.exists():
return HTMLResponse(admin_index.read_text(encoding="utf-8"))
- return HTMLResponse("
Artemis Console
")
+
+ showcase_dist = _get_showcase_dist()
+
+ # Exact static file match (js, css, images, fonts, favicon, logo)
+ if clean_path:
+ target_file = showcase_dist / clean_path
+ if target_file.is_file():
+ return FileResponse(target_file)
+
+ # Fallback for icons/logos if showcase UI hasn't been built yet
+ public_file = _workspace_root / "apps" / "showcase_ui" / "public" / clean_path
+ if public_file.is_file():
+ return FileResponse(public_file)
+
+ # Serve Showcase UI Angular SPA index.html
+ index_file = showcase_dist / "index.html"
+ if index_file.exists():
+ return HTMLResponse(index_file.read_text(encoding="utf-8"))
+
+ # Fallback message if Showcase UI is not built
+ fallback_html = """
+
+
+
+
+ Artemis Showcase UI - Not Built
+
+
+
+
+
✨ Artemis Showcase UI
+
The Showcase UI (Angular frontend) has not been built yet.
+
To compile the Showcase UI, run:
+
cd apps/showcase_ui && npm install && npm run build
+
Or launch using ./start.sh or artemis ui to build automatically.
+
Go to Admin Debug Console →
+
+
+"""
+ return HTMLResponse(fallback_html)
# ------------------------------------------------------------------------------
diff --git a/artemis/interfaces/cli/commands/doctor.py b/artemis/interfaces/cli/commands/doctor.py
index 315712b..bbf0301 100644
--- a/artemis/interfaces/cli/commands/doctor.py
+++ b/artemis/interfaces/cli/commands/doctor.py
@@ -15,6 +15,7 @@
"""System, environment, and device diagnostics (artemis doctor)."""
import platform as sys_platform
+import shutil
import subprocess
import sys
@@ -26,6 +27,7 @@ from artemis.config import (
parse_llm_config,
settings,
)
+from artemis.config.paths import ROOT_DIR
from artemis.platform import OSType, platform
from artemis.toolchain import toolchain
from artemis.utils.ocr_api import is_ocr_configured
@@ -254,6 +256,50 @@ def doctor_command() -> None:
f"scrcpy not found. ({hint})",
)
+ # 8. Node.js & npm (for UI compilation)
+ npm_path = shutil.which("npm")
+ if npm_path:
+ table.add_row(
+ "Node.js / npm",
+ "[bold green]✔ Installed[/bold green]",
+ f"{npm_path} (Ready to build Showcase UI)",
+ )
+ else:
+ hint = (
+ "winget install OpenJS.NodeJS.LTS"
+ if platform.os_type == OSType.WINDOWS
+ else "brew install node"
+ if platform.os_type == OSType.MACOS
+ else "sudo apt-get install -y nodejs npm"
+ )
+ table.add_row(
+ "Node.js / npm",
+ "[dim]⚪ Optional[/dim]",
+ f"Not found in PATH. ({hint})",
+ )
+
+ # 9. Showcase UI Build Status
+ base_dist = ROOT_DIR / "apps" / "showcase_ui" / "dist"
+ candidates = [
+ base_dist / "frontend" / "browser" / "index.html",
+ base_dist / "browser" / "index.html",
+ base_dist / "frontend" / "index.html",
+ base_dist / "index.html",
+ ]
+ found_showcase = next((p for p in candidates if p.exists()), None)
+ if found_showcase:
+ table.add_row(
+ "Showcase UI",
+ "[bold green]✔ Compiled[/bold green]",
+ f"{found_showcase.parent} (Angular ready)",
+ )
+ else:
+ table.add_row(
+ "Showcase UI",
+ "[bold yellow]○ Not Compiled[/bold yellow]",
+ "Run ./start.sh or artemis ui to auto-compile.",
+ )
+
console.print(table)
console.print()
diff --git a/artemis/interfaces/cli/commands/init.py b/artemis/interfaces/cli/commands/init.py
index d22ae3f..0dd5575 100644
--- a/artemis/interfaces/cli/commands/init.py
+++ b/artemis/interfaces/cli/commands/init.py
@@ -134,6 +134,33 @@ def init_command() -> None:
env_path = ROOT_DIR / ".env"
env_path.write_text("\n".join(env_content), encoding="utf-8")
+ # 5. Configure MCP for AI IDEs
+ console.print("\n[bold]Step 5: Connect Artemis to your AI IDE (MCP)[/bold]")
+ console.print(" [1] Antigravity / Jetski (Recommended - Default)")
+ console.print(" [2] Cursor")
+ console.print(" [3] Claude Desktop / Claude Code")
+ console.print(" [4] Windsurf")
+ console.print(" [5] All supported IDEs")
+ console.print(" [6] Skip for now")
+ mcp_choice = Prompt.ask(
+ "Select IDE for MCP auto-install", choices=["1", "2", "3", "4", "5", "6"], default="1"
+ )
+ if mcp_choice != "6":
+ target_map = {"1": "antigravity", "2": "cursor", "3": "claude", "4": "windsurf", "5": "all"}
+ target = target_map.get(mcp_choice, "antigravity")
+ try:
+ from artemis.interfaces.cli.commands.mcp import install_mcp_config
+ from mcp_server.utils import env_utils
+
+ proj_root = env_utils.get_project_root()
+ py_exe = env_utils.resolve_python_executable(proj_root)
+ installed_paths = install_mcp_config(target, py_exe, proj_root)
+ console.print(
+ f"[bold green]✔ Configured Artemis MCP in {len(installed_paths)} IDE location(s).[/bold green]"
+ )
+ except Exception as e:
+ console.print(f"[yellow]Could not auto-install MCP config: {e}[/yellow]")
+
console.print()
console.print(
Panel(
@@ -141,6 +168,8 @@ def init_command() -> None:
f"Configured [bold cyan]{provider_name}[/bold cyan] in [dim]{env_path}[/dim]\n\n"
"Try running your first task:\n"
' [bold cyan]artemis run "Open YouTube and search for Lo-Fi Hip Hop"[/bold cyan]\n\n'
+ "Or connect/update your AI IDE anytime with:\n"
+ " [bold cyan]artemis mcp --install all[/bold cyan]\n\n"
"Or run health diagnostics anytime with:\n"
" [bold cyan]artemis doctor[/bold cyan]",
title="☕ Ready to Go!",
diff --git a/artemis/interfaces/cli/commands/mcp.py b/artemis/interfaces/cli/commands/mcp.py
index 3102ee3..2b380d7 100644
--- a/artemis/interfaces/cli/commands/mcp.py
+++ b/artemis/interfaces/cli/commands/mcp.py
@@ -15,6 +15,9 @@
"""Model Context Protocol (MCP) server command (artemis mcp)."""
import json
+import os
+from pathlib import Path
+import sys
from typing import Annotated
from mcp_server.base import mcp as agent_mcp
@@ -40,7 +43,21 @@ def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict
},
}
- if client == "cursor":
+ if client in ("antigravity", "jetski"):
+ return {
+ "mcpServers": {
+ "artemis": {
+ **config_body,
+ "tools": {
+ "mobile_run_task": {"eager": True},
+ "mobile_manage_task": {"eager": True},
+ "mobile_get_device_state": {"eager": True},
+ "mobile_inspect_trace": {"eager": True},
+ },
+ }
+ }
+ }
+ elif client in ("cursor", "windsurf"):
return {"mcpServers": {"artemis": config_body}}
elif client in ("claude", "claude_code", "claude_desktop"):
return {"mcpServers": {"artemis": config_body}}
@@ -50,6 +67,68 @@ def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict
return {"mcpServers": {"artemis": config_body}}
+def _merge_json_file(file_path: Path, server_name: str, server_config: dict, key_name: str = "mcpServers") -> bool:
+ """Merges server configuration into a target JSON file without overwriting existing servers."""
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+ data: dict = {}
+ if file_path.exists() and file_path.stat().st_size > 0:
+ try:
+ data = json.loads(file_path.read_text(encoding="utf-8"))
+ except Exception:
+ data = {}
+
+ if key_name not in data or not isinstance(data.get(key_name), dict):
+ data[key_name] = {}
+
+ data[key_name][server_name] = server_config
+ file_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ return True
+
+
+def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[str]:
+ """Auto-installs/merges ARTEMIS MCP configuration into IDE config files."""
+ installed_paths: list[str] = []
+ targets = ["antigravity", "cursor", "claude", "windsurf", "openclaw"] if client == "all" else [client]
+
+ for target in targets:
+ snippet = _get_config_snippet(target, python_exe, project_root)
+ if target in ("antigravity", "jetski"):
+ server_cfg = snippet["mcpServers"]["artemis"]
+ jetski_path = Path.home() / ".gemini" / "jetski" / "mcp_config.json"
+ config_path = Path.home() / ".gemini" / "config" / "mcp_config.json"
+ _merge_json_file(jetski_path, "artemis", server_cfg)
+ _merge_json_file(config_path, "artemis", server_cfg)
+ installed_paths.extend([str(jetski_path), str(config_path)])
+ elif target in ("claude", "claude_code", "claude_desktop"):
+ server_cfg = snippet["mcpServers"]["artemis"]
+ if sys.platform == "darwin":
+ claude_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
+ elif sys.platform == "win32":
+ appdata = os.environ.get("APPDATA") or str(Path.home() / "AppData" / "Roaming")
+ claude_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
+ else:
+ claude_path = Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
+ _merge_json_file(claude_path, "artemis", server_cfg)
+ installed_paths.append(str(claude_path))
+ elif target == "cursor":
+ server_cfg = snippet["mcpServers"]["artemis"]
+ cursor_path = Path.home() / ".cursor" / "mcp.json"
+ _merge_json_file(cursor_path, "artemis", server_cfg)
+ installed_paths.append(str(cursor_path))
+ elif target == "windsurf":
+ server_cfg = snippet["mcpServers"]["artemis"]
+ windsurf_path = Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
+ _merge_json_file(windsurf_path, "artemis", server_cfg)
+ installed_paths.append(str(windsurf_path))
+ elif target == "openclaw":
+ plugin_cfg = snippet["plugins"]["artemis_mcp"]
+ openclaw_path = Path.home() / ".openclaw" / "openclaw.json"
+ _merge_json_file(openclaw_path, "artemis_mcp", plugin_cfg, key_name="plugins")
+ installed_paths.append(str(openclaw_path))
+
+ return installed_paths
+
+
def mcp_command(
server_type: Annotated[
str,
@@ -81,12 +160,20 @@ def mcp_command(
help="Port number when running with SSE transport.",
),
] = 8001,
+ install_config: Annotated[
+ str | None,
+ typer.Option(
+ "--install",
+ "-i",
+ help="Auto-install and merge ARTEMIS MCP configuration into 'antigravity', 'claude', 'cursor', 'windsurf', 'openclaw', or 'all'.",
+ ),
+ ] = None,
generate_config: Annotated[
str | None,
typer.Option(
"--generate-config",
"-g",
- help="Output ready-to-use MCP configuration JSON for 'cursor', 'claude', 'openclaw', or 'all'.",
+ help="Output ready-to-use MCP configuration JSON for 'antigravity', 'cursor', 'claude', 'windsurf', 'openclaw', or 'all'.",
),
] = None,
) -> None:
@@ -94,16 +181,46 @@ def mcp_command(
project_root = env_utils.get_project_root()
python_exe = env_utils.resolve_python_executable(project_root)
+ if install_config:
+ client = install_config.lower()
+ if client not in (
+ "antigravity",
+ "jetski",
+ "claude",
+ "claude_code",
+ "claude_desktop",
+ "cursor",
+ "windsurf",
+ "openclaw",
+ "all",
+ ):
+ console.print(
+ f"[bold red]Unsupported install target: '{install_config}'. Use 'antigravity', 'claude', 'cursor', 'windsurf', 'openclaw', or 'all'.[/bold red]"
+ )
+ raise typer.Exit(1)
+ installed_paths = install_mcp_config(client, python_exe, project_root)
+ console.print("[bold green]✔ Successfully installed ARTEMIS MCP server configuration to:[/bold green]")
+ for path in installed_paths:
+ console.print(f" • [cyan]{path}[/cyan]")
+ console.print("\n[dim]Please restart or reload your IDE window to activate the Artemis MCP tools.[/dim]")
+ raise typer.Exit(0)
+
if generate_config:
client = generate_config.lower()
if client == "all":
all_configs = {
+ "antigravity (~/.gemini/jetski/mcp_config.json)": _get_config_snippet(
+ "antigravity", python_exe, project_root
+ ),
"cursor (.cursor/mcp.json)": _get_config_snippet(
"cursor", python_exe, project_root
),
"claude (claude_desktop_config.json)": _get_config_snippet(
"claude", python_exe, project_root
),
+ "windsurf (~/.codeium/windsurf/mcp_config.json)": _get_config_snippet(
+ "windsurf", python_exe, project_root
+ ),
"openclaw (openclaw.json)": _get_config_snippet(
"openclaw", python_exe, project_root
),
diff --git a/artemis/interfaces/cli/commands/ui.py b/artemis/interfaces/cli/commands/ui.py
index 7216e89..307620e 100644
--- a/artemis/interfaces/cli/commands/ui.py
+++ b/artemis/interfaces/cli/commands/ui.py
@@ -94,6 +94,29 @@ def ui_command(
except Exception:
pass
+ base_dist = ROOT_DIR / "apps" / "showcase_ui" / "dist"
+ candidates = [
+ base_dist / "frontend" / "browser" / "index.html",
+ base_dist / "browser" / "index.html",
+ base_dist / "frontend" / "index.html",
+ base_dist / "index.html",
+ ]
+ if not any(p.exists() for p in candidates):
+ import shutil
+ import subprocess
+
+ if shutil.which("npm"):
+ console.print(
+ " [yellow]🎨 Showcase UI build not found. Compiling Angular Showcase UI...[/yellow]"
+ )
+ showcase_dir = ROOT_DIR / "apps" / "showcase_ui"
+ try:
+ subprocess.run(["npm", "install", "--silent"], cwd=showcase_dir, check=True)
+ subprocess.run(["npm", "run", "build"], cwd=showcase_dir, check=True)
+ console.print(" [green]✓ Showcase UI built successfully.[/green]\n")
+ except Exception as e:
+ console.print(f" [red]⚠ Failed to auto-build Showcase UI: {e}[/red]\n")
+
stop_event = threading.Event()
if open_browser:
t = threading.Thread(
diff --git a/mcp_server/README.md b/mcp_server/README.md
index f59ccd1..1bf94e8 100644
--- a/mcp_server/README.md
+++ b/mcp_server/README.md
@@ -66,4 +66,14 @@ artemis mcp
```
### IDE Configuration
-Run `artemis mcp --generate-config all` to generate ready-to-use configuration JSON for Cursor, Claude Code, OpenClaw, and Windsurf.
+
+#### One-Click Auto Install (Recommended)
+Run the automated installer to detect and configure your IDE (Antigravity, Cursor, Claude Code/Desktop, OpenClaw):
+```bash
+artemis mcp --install all
+# Or install specifically for Antigravity:
+artemis mcp --install antigravity
+```
+
+#### Manual JSON Config Generation
+Run `artemis mcp --generate-config antigravity` (or `all`) to output ready-to-use configuration JSON with resolved `.venv` Python and project paths.
diff --git a/scripts/install_deps.ps1 b/scripts/install_deps.ps1
index 95d713d..f2e708d 100644
--- a/scripts/install_deps.ps1
+++ b/scripts/install_deps.ps1
@@ -40,6 +40,9 @@ function Update-EnvironmentPath {
"$env:LOCALAPPDATA\Microsoft\WinGet\Links",
"$env:LOCALAPPDATA\Android\Sdk\platform-tools",
"$env:ProgramFiles\Android\platform-tools",
+ "$env:ProgramFiles\nodejs",
+ "${env:ProgramFiles(x86)}\nodejs",
+ "$env:APPDATA\npm",
"C:\ProgramData\chocolatey\bin",
"$env:USERPROFILE\scoop\shims",
"$env:USERPROFILE\.local\bin",
@@ -134,9 +137,42 @@ if (-not (Test-Path ".env")) {
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")
+# 5. Check and Build Showcase UI (Angular)
+Write-Host "`n5. Checking Showcase UI Build (Angular)..." -ForegroundColor Yellow
+$ShowcaseIndex = "$RootDir\apps\showcase_ui\dist\frontend\browser\index.html"
+$ShowcaseIndexAlt1 = "$RootDir\apps\showcase_ui\dist\browser\index.html"
+$ShowcaseIndexAlt2 = "$RootDir\apps\showcase_ui\dist\index.html"
+if ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Path $ShowcaseIndexAlt1)) -and (-not (Test-Path $ShowcaseIndexAlt2))) {
+ if (-not (Test-CommandExists "npm")) {
+ if (Test-CommandExists "winget") {
+ Write-Host " ⚡ Node.js/npm not found. Installing Node.js LTS via WinGet..." -ForegroundColor Cyan
+ winget install --id OpenJS.NodeJS.LTS -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
+ Update-EnvironmentPath
+ }
+ }
+
+ if (Test-CommandExists "npm") {
+ Write-Host " 🎨 Compiling Angular Showcase UI..." -ForegroundColor Cyan
+ Push-Location "$RootDir\apps\showcase_ui"
+ try {
+ npm install --silent
+ npm run build
+ Write-Host " ✔ Showcase UI compiled successfully." -ForegroundColor Green
+ } catch {
+ Write-Host " ⚠ Failed to build Showcase UI: $_" -ForegroundColor DarkYellow
+ } finally {
+ Pop-Location
+ }
+ } else {
+ Write-Host " ⚠ Could not find npm. Showcase UI will show fallback notice on launch." -ForegroundColor DarkYellow
+ }
+} else {
+ Write-Host " ✔ Showcase UI build already exists." -ForegroundColor Green
+}
+
+# 6. Toolchain Readiness Summary
+Write-Host "`n6. Toolchain Readiness Summary:" -ForegroundColor Yellow
+$tools = @("adb", "ffmpeg", "scrcpy", "uv", "npm")
foreach ($t in $tools) {
if (Test-CommandExists $t) {
$p = (Get-Command $t).Source
diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh
index 421faf1..f48fb94 100755
--- a/scripts/install_deps.sh
+++ b/scripts/install_deps.sh
@@ -247,11 +247,47 @@ setup_env_file() {
fi
}
+# Function to check and build Showcase UI
+setup_showcase_ui() {
+ echo -e "\n${BOLD}6. Checking Showcase UI Build (Angular)...${NC}"
+ local SHOWCASE_INDEX="${ROOT_DIR}/apps/showcase_ui/dist/frontend/browser/index.html"
+ local SHOWCASE_INDEX_ALT1="${ROOT_DIR}/apps/showcase_ui/dist/browser/index.html"
+ local SHOWCASE_INDEX_ALT2="${ROOT_DIR}/apps/showcase_ui/dist/index.html"
+ if [ ! -f "${SHOWCASE_INDEX}" ] && [ ! -f "${SHOWCASE_INDEX_ALT1}" ] && [ ! -f "${SHOWCASE_INDEX_ALT2}" ]; then
+ if ! has_cmd npm; then
+ echo -e " ${YELLOW}⚡ npm/Node.js not found. Auto-installing Node.js...${NC}"
+ if [ "${OS_TYPE}" = "Darwin" ] && has_cmd brew; then
+ brew install node >/dev/null 2>&1 || true
+ elif [ "${OS_TYPE}" = "Linux" ]; then
+ local SUDO_CMD=""
+ if [ "$(id -u)" -ne 0 ] && has_cmd sudo; then SUDO_CMD="sudo"; fi
+ if has_cmd apt-get; then
+ ${SUDO_CMD} apt-get update -qq && ${SUDO_CMD} apt-get install -y -qq nodejs npm >/dev/null 2>&1 || true
+ elif has_cmd dnf; then
+ ${SUDO_CMD} dnf install -y nodejs npm >/dev/null 2>&1 || true
+ elif has_cmd pacman; then
+ ${SUDO_CMD} pacman -S --noconfirm nodejs npm >/dev/null 2>&1 || true
+ fi
+ fi
+ fi
+
+ if has_cmd npm; then
+ echo -e " ${YELLOW}🎨 Building Angular Showcase UI...${NC}"
+ (cd "${ROOT_DIR}/apps/showcase_ui" && npm install --silent && npm run build)
+ echo -e " ${GREEN}✓ Showcase UI compiled successfully.${NC}"
+ else
+ echo -e " ${YELLOW}⚠ Could not find npm. Showcase UI will show fallback notice on launch.${NC}"
+ fi
+ else
+ echo -e " ${GREEN}✓ Showcase UI build already exists.${NC}"
+ fi
+}
+
# Function to display system readiness report
verify_readiness() {
- echo -e "\n${BOLD}6. Toolchain Readiness Summary:${NC}"
+ echo -e "\n${BOLD}7. Toolchain Readiness Summary:${NC}"
- local tools=("adb" "ffmpeg" "scrcpy" "uv" "python3")
+ local tools=("adb" "ffmpeg" "scrcpy" "uv" "python3" "npm")
for t in "${tools[@]}"; do
if has_cmd "$t"; then
local loc
@@ -268,6 +304,7 @@ install_system_packages
ensure_uv
setup_python_env
setup_env_file
+setup_showcase_ui
verify_readiness
echo -e "\n${BOLD}${CYAN}======================================================${NC}"
diff --git a/scripts/start.ps1 b/scripts/start.ps1
index 99c316a..5d449f3 100644
--- a/scripts/start.ps1
+++ b/scripts/start.ps1
@@ -35,6 +35,9 @@ function Update-EnvironmentPath {
"$env:LOCALAPPDATA\Microsoft\WinGet\Links",
"$env:LOCALAPPDATA\Android\Sdk\platform-tools",
"$env:ProgramFiles\Android\platform-tools",
+ "$env:ProgramFiles\nodejs",
+ "${env:ProgramFiles(x86)}\nodejs",
+ "$env:APPDATA\npm",
"C:\ProgramData\chocolatey\bin",
"$env:USERPROFILE\scoop\shims",
"$env:USERPROFILE\.local\bin",
@@ -97,8 +100,39 @@ if (-not (Test-Path ".env")) {
Write-Host " 📦 Synchronizing Python runtime and project dependencies..." -ForegroundColor Cyan
uv sync --quiet
-# 6. Launch unified Showcase UI & open browser
+# 6. Check and build Showcase UI if not already compiled
+$ShowcaseIndex = "$RootDir\apps\showcase_ui\dist\frontend\browser\index.html"
+$ShowcaseIndexAlt1 = "$RootDir\apps\showcase_ui\dist\browser\index.html"
+$ShowcaseIndexAlt2 = "$RootDir\apps\showcase_ui\dist\index.html"
+if ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Path $ShowcaseIndexAlt1)) -and (-not (Test-Path $ShowcaseIndexAlt2))) {
+ if (-not (Test-CommandExists "npm")) {
+ if (Test-CommandExists "winget") {
+ Write-Host " ⚡ Node.js/npm not found. Installing Node.js LTS via WinGet..." -ForegroundColor Yellow
+ winget install --id OpenJS.NodeJS.LTS -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
+ Update-EnvironmentPath
+ }
+ }
+
+ if (Test-CommandExists "npm") {
+ Write-Host " 🎨 Showcase UI build not found. Compiling Angular Showcase UI..." -ForegroundColor Yellow
+ Push-Location "$RootDir\apps\showcase_ui"
+ try {
+ npm install --silent
+ npm run build
+ Write-Host " ✔ Showcase UI compiled successfully." -ForegroundColor Green
+ } catch {
+ Write-Host " ⚠ Failed to build Showcase UI: $_" -ForegroundColor DarkYellow
+ } finally {
+ Pop-Location
+ }
+ } else {
+ Write-Host " ⚠ Node.js/npm not found. Please install Node.js (>= 18) to compile Showcase UI." -ForegroundColor DarkYellow
+ }
+}
+
+# 7. Launch unified Showcase UI & open browser
Write-Host " 🚀 Launching Artemis Showcase UI & Admin Console..." -ForegroundColor Green
+Write-Host " 💡 Tip: Connect to Antigravity / Claude Desktop anytime: artemis mcp --install all" -ForegroundColor Cyan
if ($NoOpen) {
uv run artemis ui --port $Port --no-open
} else {
diff --git a/start.sh b/start.sh
index 9c98493..5081e5d 100755
--- a/start.sh
+++ b/start.sh
@@ -112,6 +112,48 @@ fi
echo -e " ${BLUE}📦 Synchronizing Python runtime and project dependencies...${NC}"
uv sync --quiet
-# 6. Launch unified Showcase UI & auto-open browser
+# 6. Check and build Showcase UI if not already compiled
+SHOWCASE_INDEX="${SCRIPT_DIR}/apps/showcase_ui/dist/frontend/browser/index.html"
+SHOWCASE_INDEX_ALT1="${SCRIPT_DIR}/apps/showcase_ui/dist/browser/index.html"
+SHOWCASE_INDEX_ALT2="${SCRIPT_DIR}/apps/showcase_ui/dist/index.html"
+if [ ! -f "${SHOWCASE_INDEX}" ] && [ ! -f "${SHOWCASE_INDEX_ALT1}" ] && [ ! -f "${SHOWCASE_INDEX_ALT2}" ]; then
+ if ! command -v npm >/dev/null 2>&1; then
+ OS_NAME="$(uname -s)"
+ echo -e " ${YELLOW}⚡ npm/Node.js not found. Auto-installing Node.js for Showcase UI compilation...${NC}"
+ if [ "${OS_NAME}" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
+ brew install node >/dev/null 2>&1 || true
+ elif [ "${OS_NAME}" = "Linux" ]; then
+ if command -v apt-get >/dev/null 2>&1; then
+ if [ "$(id -u)" -eq 0 ]; then
+ apt-get update -qq && apt-get install -y -qq nodejs npm >/dev/null 2>&1 || true
+ elif command -v sudo >/dev/null 2>&1; then
+ sudo -n apt-get update -qq && sudo -n apt-get install -y -qq nodejs npm >/dev/null 2>&1 || true
+ fi
+ elif command -v dnf >/dev/null 2>&1; then
+ if [ "$(id -u)" -eq 0 ]; then
+ dnf install -y nodejs npm >/dev/null 2>&1 || true
+ elif command -v sudo >/dev/null 2>&1; then
+ sudo -n dnf install -y nodejs npm >/dev/null 2>&1 || true
+ fi
+ elif command -v pacman >/dev/null 2>&1; then
+ if [ "$(id -u)" -eq 0 ]; then
+ pacman -S --noconfirm nodejs npm >/dev/null 2>&1 || true
+ elif command -v sudo >/dev/null 2>&1; then
+ sudo -n pacman -S --noconfirm nodejs npm >/dev/null 2>&1 || true
+ fi
+ fi
+ fi
+ fi
+
+ if command -v npm >/dev/null 2>&1; then
+ echo -e " ${YELLOW}🎨 Showcase UI build not found. Compiling Angular Showcase UI...${NC}"
+ (cd "${SCRIPT_DIR}/apps/showcase_ui" && npm install --silent && npm run build)
+ else
+ echo -e " ${YELLOW}⚠ Could not auto-install Node.js. Showcase UI will serve fallback build notice.${NC}"
+ fi
+fi
+
+# 7. Launch unified Showcase UI & auto-open browser
echo -e " ${GREEN}🚀 Launching Artemis Showcase UI & Admin Console...${NC}"
+echo -e " ${CYAN}💡 Tip: Connect to Antigravity / Claude Desktop anytime: ${BOLD}artemis mcp --install all${NC}"
exec uv run artemis ui --open "$@"
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
index 81159a2..c486ebe 100644
--- a/tests/unit/test_cli.py
+++ b/tests/unit/test_cli.py
@@ -78,3 +78,49 @@ def test_cli_mcp_generate_config():
assert result.exit_code == 0
assert "mcpServers" in result.output
assert "mcp_server" in result.output
+
+
+def test_cli_mcp_generate_config_antigravity():
+ """Verify 'artemis mcp --generate-config antigravity' produces valid configuration with tools."""
+ result = runner.invoke(app, ["mcp", "--generate-config", "antigravity"])
+ assert result.exit_code == 0
+ assert "mcpServers" in result.output
+ assert "mobile_run_task" in result.output
+ assert "eager" in result.output
+
+
+def test_cli_mcp_generate_config_all():
+ """Verify 'artemis mcp --generate-config all' includes antigravity, cursor, windsurf, and claude."""
+ result = runner.invoke(app, ["mcp", "--generate-config", "all"])
+ assert result.exit_code == 0
+ assert "antigravity" in result.output
+ assert "cursor" in result.output
+ assert "windsurf" in result.output
+ assert "claude" in result.output
+
+
+def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
+ """Verify 'artemis mcp --install antigravity' writes configuration into target file."""
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
+ result = runner.invoke(app, ["mcp", "--install", "antigravity"])
+ assert result.exit_code == 0
+ assert "Successfully installed ARTEMIS MCP server configuration" in result.output
+ jetski_file = tmp_path / ".gemini" / "jetski" / "mcp_config.json"
+ assert jetski_file.exists()
+ import json
+ data = json.loads(jetski_file.read_text())
+ assert "artemis" in data["mcpServers"]
+ assert "mobile_run_task" in data["mcpServers"]["artemis"]["tools"]
+
+
+def test_cli_mcp_install_all(tmp_path, monkeypatch):
+ """Verify 'artemis mcp --install all' installs to all supported IDE locations."""
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
+ result = runner.invoke(app, ["mcp", "--install", "all"])
+ assert result.exit_code == 0
+ assert "Successfully installed ARTEMIS MCP server configuration" in result.output
+ assert (tmp_path / ".cursor" / "mcp.json").exists()
+ assert (tmp_path / ".codeium" / "windsurf" / "mcp_config.json").exists()
+ assert (tmp_path / ".openclaw" / "openclaw.json").exists()
+
+