feat(ui): ensure cross-platform one-click setup and default Showcase UI routing

- Add explicit root route (/) in server.py to guarantee Showcase UI is served by default, routing /admin to the Admin Debug Console
- Enhance dynamic Angular dist detection across multiple standard output paths
- Add automated Node.js/npm detection and installation fallbacks to start scripts and installers across macOS, Linux, and Windows
- Automatically build Showcase UI when launching via start.sh, start.bat/start.ps1, or artemis ui if frontend artifacts are missing
- Add Node.js / npm and Showcase UI status inspection rows to artemis doctor
- Support artemis mcp --install for one-click IDE integration and add make build-ui target
This commit is contained in:
somew1nd committed 2026-08-18 17:22:49 -07:00
1 parent 6fa4768893
commit fc6ddad655
15 files changed
+598 -64

No files matched your search

+1
View File
@@ -81,6 +81,7 @@ data_engine.db
data_engine.db-journal data_engine.db-journal
.ipc_port .ipc_port
.ls_address .ls_address
.jetski_ls_address
# Environment & Secrets # Environment & Secrets
.env .env
+7 -2
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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 help: ## Show this help message
@echo 'Usage: make [target]' @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 ui: ## Launch the unified Showcase UI & Admin Console in browser
@uv run artemis ui --open @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 doctor: ## Run system, device, and toolchain diagnostics
@uv run artemis doctor @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..." @echo "⚡ Running one-click dependency installer..."
@bash scripts/install_deps.sh @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..." @echo "🚀 Setting up project..."
@$(MAKE) install @$(MAKE) install
@$(MAKE) build-ui
@$(MAKE) precommit-install @$(MAKE) precommit-install
@echo "" @echo ""
@echo "✅ Setup complete! You're ready to start developing." @echo "✅ Setup complete! You're ready to start developing."
+21 -9
View File
@@ -105,28 +105,40 @@ start.bat
ARTEMIS includes a native **Model Context Protocol (MCP)** server. Connect your real phone directly into AI IDEs: 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 ```bash
artemis mcp --generate-config antigravity # Auto-install for Antigravity / Jetski:
# Or generate configs for all IDEs: artemis mcp --install antigravity
artemis mcp --generate-config all
# 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 ```json
{ {
"mcpServers": { "mcpServers": {
"artemis": { "artemis": {
"command": "python", "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"], "args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis", "cwd": "/path/to/artemis",
"env": { "env": {
"PYTHONUNBUFFERED": "1" "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": { "mcpServers": {
"artemis": { "artemis": {
"command": "python", "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"], "args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis" "cwd": "/path/to/artemis"
} }
+21 -9
View File
@@ -107,28 +107,40 @@ start.bat
ARTEMIS 内置原生 **Model Context Protocol (MCP)** 服务。只需将以下配置加入你的 IDE 配置文件中,即可在编写代码时直接驱动真机: ARTEMIS 内置原生 **Model Context Protocol (MCP)** 服务。只需将以下配置加入你的 IDE 配置文件中,即可在编写代码时直接驱动真机:
### 1. 生成或查看配置 ### 1. 一键自动安装到 IDE(推荐)
运行内置命令一键获取当前环境的完整配置 JSON: 运行内置安装命令,即可自动配置或更新你的 AI IDE(会自动解析并填写虚拟环境 Python 和项目的绝对路径):
```bash ```bash
artemis mcp --generate-config antigravity # 一键安装到 Antigravity / Jetski:
# 或生成所有 IDE 配置: artemis mcp --install antigravity
artemis mcp --generate-config all
# 或一键安装到所有检测到的 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 ```json
{ {
"mcpServers": { "mcpServers": {
"artemis": { "artemis": {
"command": "python", "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"], "args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis", "cwd": "/path/to/artemis",
"env": { "env": {
"PYTHONUNBUFFERED": "1" "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": { "mcpServers": {
"artemis": { "artemis": {
"command": "python", "command": "/path/to/artemis/.venv/bin/python",
"args": ["-m", "mcp_server"], "args": ["-m", "mcp_server"],
"cwd": "/path/to/artemis" "cwd": "/path/to/artemis"
} }
+118 -34
View File
@@ -136,50 +136,134 @@ except ImportError:
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Showcase UI (Angular 19) Unified Single-Port SPA Static Hosting # Showcase UI (Angular 19) Unified Single-Port SPA Static Hosting
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
_showcase_dist = _workspace_root / "apps" / "showcase_ui" / "dist" / "frontend" / "browser" def _get_showcase_dist() -> Path:
if not _showcase_dist.exists(): """Return the Showcase UI Angular build directory."""
_showcase_dist = _workspace_root / "apps" / "showcase_ui" / "dist" 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) @app.get("/", include_in_schema=False)
async def serve_showcase_spa(full_path: str): async def serve_showcase_root():
# Do not intercept API, media, or replay paths """Explicitly serve the Showcase UI at the root path by default."""
if ( return await serve_showcase_spa("")
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
raise HTTPException(status_code=404, detail="Endpoint not found")
# Admin / Debug Console routes @app.get("/{full_path:path}", include_in_schema=False)
if full_path in ("admin", "debug"): async def serve_showcase_spa(full_path: str):
admin_index = _admin_console_dir / "index.html" # Do not intercept API, media, or replay paths
if admin_index.exists(): if (
return HTMLResponse(admin_index.read_text(encoding="utf-8")) 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) raise HTTPException(status_code=404, detail="Endpoint not found")
target_file = _showcase_dist / full_path
if full_path and target_file.is_file():
return FileResponse(target_file)
# Default fallback to Angular SPA index.html clean_path = full_path.strip("/")
index_file = _showcase_dist / "index.html"
return HTMLResponse(index_file.read_text(encoding="utf-8"))
else:
@app.get("/", response_class=HTMLResponse, include_in_schema=False) # Admin / Debug Console routes
async def fallback_root(): if (
clean_path in ("admin", "debug")
or clean_path.startswith("admin/")
or clean_path.startswith("debug/")
):
admin_index = _admin_console_dir / "index.html" admin_index = _admin_console_dir / "index.html"
if admin_index.exists(): if admin_index.exists():
return HTMLResponse(admin_index.read_text(encoding="utf-8")) return HTMLResponse(admin_index.read_text(encoding="utf-8"))
return HTMLResponse("<h1>Artemis Console</h1>")
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 = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Artemis Showcase UI - Not Built</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.card {
background: #1e293b;
padding: 2.5rem;
border-radius: 1rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5);
max-width: 500px;
text-align: center;
border: 1px solid #334155;
}
h1 { color: #38bdf8; margin-top: 0; }
code {
background: #0f172a;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
color: #e2e8f0;
font-size: 0.9em;
}
.btn {
display: inline-block;
margin-top: 1.5rem;
padding: 0.75rem 1.5rem;
background: #38bdf8;
color: #0f172a;
font-weight: bold;
text-decoration: none;
border-radius: 0.5rem;
transition: background 0.2s;
}
.btn:hover { background: #0284c7; color: #fff; }
</style>
</head>
<body>
<div class="card">
<h1>✨ Artemis Showcase UI</h1>
<p>The Showcase UI (Angular frontend) has not been built yet.</p>
<p>To compile the Showcase UI, run:</p>
<p><code>cd apps/showcase_ui && npm install && npm run build</code></p>
<p>Or launch using <code>./start.sh</code> or <code>artemis ui</code> to build automatically.</p>
<a class="btn" href="/admin">Go to Admin Debug Console →</a>
</div>
</body>
</html>"""
return HTMLResponse(fallback_html)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
+46
View File
@@ -15,6 +15,7 @@
"""System, environment, and device diagnostics (artemis doctor).""" """System, environment, and device diagnostics (artemis doctor)."""
import platform as sys_platform import platform as sys_platform
import shutil
import subprocess import subprocess
import sys import sys
@@ -26,6 +27,7 @@ from artemis.config import (
parse_llm_config, parse_llm_config,
settings, settings,
) )
from artemis.config.paths import ROOT_DIR
from artemis.platform import OSType, platform from artemis.platform import OSType, platform
from artemis.toolchain import toolchain from artemis.toolchain import toolchain
from artemis.utils.ocr_api import is_ocr_configured from artemis.utils.ocr_api import is_ocr_configured
@@ -254,6 +256,50 @@ def doctor_command() -> None:
f"scrcpy not found. ({hint})", 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(table)
console.print() console.print()
+29
View File
@@ -134,6 +134,33 @@ def init_command() -> None:
env_path = ROOT_DIR / ".env" env_path = ROOT_DIR / ".env"
env_path.write_text("\n".join(env_content), encoding="utf-8") 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()
console.print( console.print(
Panel( Panel(
@@ -141,6 +168,8 @@ def init_command() -> None:
f"Configured [bold cyan]{provider_name}[/bold cyan] in [dim]{env_path}[/dim]\n\n" f"Configured [bold cyan]{provider_name}[/bold cyan] in [dim]{env_path}[/dim]\n\n"
"Try running your first task:\n" "Try running your first task:\n"
' [bold cyan]artemis run "Open YouTube and search for Lo-Fi Hip Hop"[/bold cyan]\n\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" "Or run health diagnostics anytime with:\n"
" [bold cyan]artemis doctor[/bold cyan]", " [bold cyan]artemis doctor[/bold cyan]",
title="☕ Ready to Go!", title="☕ Ready to Go!",
+119 -2
View File
@@ -15,6 +15,9 @@
"""Model Context Protocol (MCP) server command (artemis mcp).""" """Model Context Protocol (MCP) server command (artemis mcp)."""
import json import json
import os
from pathlib import Path
import sys
from typing import Annotated from typing import Annotated
from mcp_server.base import mcp as agent_mcp 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}} return {"mcpServers": {"artemis": config_body}}
elif client in ("claude", "claude_code", "claude_desktop"): elif client in ("claude", "claude_code", "claude_desktop"):
return {"mcpServers": {"artemis": config_body}} 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}} 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( def mcp_command(
server_type: Annotated[ server_type: Annotated[
str, str,
@@ -81,12 +160,20 @@ def mcp_command(
help="Port number when running with SSE transport.", help="Port number when running with SSE transport.",
), ),
] = 8001, ] = 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[ generate_config: Annotated[
str | None, str | None,
typer.Option( typer.Option(
"--generate-config", "--generate-config",
"-g", "-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,
) -> None: ) -> None:
@@ -94,16 +181,46 @@ def mcp_command(
project_root = env_utils.get_project_root() project_root = env_utils.get_project_root()
python_exe = env_utils.resolve_python_executable(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: if generate_config:
client = generate_config.lower() client = generate_config.lower()
if client == "all": if client == "all":
all_configs = { all_configs = {
"antigravity (~/.gemini/jetski/mcp_config.json)": _get_config_snippet(
"antigravity", python_exe, project_root
),
"cursor (.cursor/mcp.json)": _get_config_snippet( "cursor (.cursor/mcp.json)": _get_config_snippet(
"cursor", python_exe, project_root "cursor", python_exe, project_root
), ),
"claude (claude_desktop_config.json)": _get_config_snippet( "claude (claude_desktop_config.json)": _get_config_snippet(
"claude", python_exe, project_root "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 (openclaw.json)": _get_config_snippet(
"openclaw", python_exe, project_root "openclaw", python_exe, project_root
), ),
+23
View File
@@ -94,6 +94,29 @@ def ui_command(
except Exception: except Exception:
pass 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() stop_event = threading.Event()
if open_browser: if open_browser:
t = threading.Thread( t = threading.Thread(
+11 -1
View File
@@ -66,4 +66,14 @@ artemis mcp
``` ```
### IDE Configuration ### 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.
+39 -3
View File
@@ -40,6 +40,9 @@ function Update-EnvironmentPath {
"$env:LOCALAPPDATA\Microsoft\WinGet\Links", "$env:LOCALAPPDATA\Microsoft\WinGet\Links",
"$env:LOCALAPPDATA\Android\Sdk\platform-tools", "$env:LOCALAPPDATA\Android\Sdk\platform-tools",
"$env:ProgramFiles\Android\platform-tools", "$env:ProgramFiles\Android\platform-tools",
"$env:ProgramFiles\nodejs",
"${env:ProgramFiles(x86)}\nodejs",
"$env:APPDATA\npm",
"C:\ProgramData\chocolatey\bin", "C:\ProgramData\chocolatey\bin",
"$env:USERPROFILE\scoop\shims", "$env:USERPROFILE\scoop\shims",
"$env:USERPROFILE\.local\bin", "$env:USERPROFILE\.local\bin",
@@ -134,9 +137,42 @@ if (-not (Test-Path ".env")) {
Write-Host " ✔ .env configuration file exists." -ForegroundColor Green Write-Host " ✔ .env configuration file exists." -ForegroundColor Green
} }
# 5. Toolchain Readiness Summary # 5. Check and Build Showcase UI (Angular)
Write-Host "`n5. Toolchain Readiness Summary:" -ForegroundColor Yellow Write-Host "`n5. Checking Showcase UI Build (Angular)..." -ForegroundColor Yellow
$tools = @("adb", "ffmpeg", "scrcpy", "uv") $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) { foreach ($t in $tools) {
if (Test-CommandExists $t) { if (Test-CommandExists $t) {
$p = (Get-Command $t).Source $p = (Get-Command $t).Source
+39 -2
View File
@@ -247,11 +247,47 @@ setup_env_file() {
fi 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 # Function to display system readiness report
verify_readiness() { 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 for t in "${tools[@]}"; do
if has_cmd "$t"; then if has_cmd "$t"; then
local loc local loc
@@ -268,6 +304,7 @@ install_system_packages
ensure_uv ensure_uv
setup_python_env setup_python_env
setup_env_file setup_env_file
setup_showcase_ui
verify_readiness verify_readiness
echo -e "\n${BOLD}${CYAN}======================================================${NC}" echo -e "\n${BOLD}${CYAN}======================================================${NC}"
+35 -1
View File
@@ -35,6 +35,9 @@ function Update-EnvironmentPath {
"$env:LOCALAPPDATA\Microsoft\WinGet\Links", "$env:LOCALAPPDATA\Microsoft\WinGet\Links",
"$env:LOCALAPPDATA\Android\Sdk\platform-tools", "$env:LOCALAPPDATA\Android\Sdk\platform-tools",
"$env:ProgramFiles\Android\platform-tools", "$env:ProgramFiles\Android\platform-tools",
"$env:ProgramFiles\nodejs",
"${env:ProgramFiles(x86)}\nodejs",
"$env:APPDATA\npm",
"C:\ProgramData\chocolatey\bin", "C:\ProgramData\chocolatey\bin",
"$env:USERPROFILE\scoop\shims", "$env:USERPROFILE\scoop\shims",
"$env:USERPROFILE\.local\bin", "$env:USERPROFILE\.local\bin",
@@ -97,8 +100,39 @@ if (-not (Test-Path ".env")) {
Write-Host " 📦 Synchronizing Python runtime and project dependencies..." -ForegroundColor Cyan Write-Host " 📦 Synchronizing Python runtime and project dependencies..." -ForegroundColor Cyan
uv sync --quiet 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 " 🚀 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) { if ($NoOpen) {
uv run artemis ui --port $Port --no-open uv run artemis ui --port $Port --no-open
} else { } else {
+43 -1
View File
@@ -112,6 +112,48 @@ fi
echo -e " ${BLUE}📦 Synchronizing Python runtime and project dependencies...${NC}" echo -e " ${BLUE}📦 Synchronizing Python runtime and project dependencies...${NC}"
uv sync --quiet 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 " ${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 "$@" exec uv run artemis ui --open "$@"
+46
View File
@@ -78,3 +78,49 @@ def test_cli_mcp_generate_config():
assert result.exit_code == 0 assert result.exit_code == 0
assert "mcpServers" in result.output assert "mcpServers" in result.output
assert "mcp_server" 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()