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:
1 parent
6fa4768893
commit
fc6ddad655
15 files changed
+598
-64
No files matched your search
@@ -81,6 +81,7 @@ data_engine.db
|
||||
data_engine.db-journal
|
||||
.ipc_port
|
||||
.ls_address
|
||||
.jetski_ls_address
|
||||
|
||||
# Environment & Secrets
|
||||
.env
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
+21
-9
@@ -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"
|
||||
}
|
||||
|
||||
+118
-34
@@ -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("<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)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
+11
-1
@@ -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.
|
||||
@@ -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
|
||||
|
||||
+39
-2
@@ -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}"
|
||||
|
||||
+35
-1
@@ -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 {
|
||||
|
||||
@@ -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 "$@"
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user