From fc6ddad65501cc0b9f6e9aa0c5eba0956ae15cc3 Mon Sep 17 00:00:00 2001 From: somew1nd Date: Tue, 18 Aug 2026 17:22:49 -0700 Subject: [PATCH] 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 --- .gitignore | 1 + Makefile | 9 +- README.md | 30 +++-- README_CN.md | 30 +++-- apps/admin_console/server.py | 152 +++++++++++++++++----- artemis/interfaces/cli/commands/doctor.py | 46 +++++++ artemis/interfaces/cli/commands/init.py | 29 +++++ artemis/interfaces/cli/commands/mcp.py | 121 ++++++++++++++++- artemis/interfaces/cli/commands/ui.py | 23 ++++ mcp_server/README.md | 12 +- scripts/install_deps.ps1 | 42 +++++- scripts/install_deps.sh | 41 +++++- scripts/start.ps1 | 36 ++++- start.sh | 44 ++++++- tests/unit/test_cli.py | 46 +++++++ 15 files changed, 598 insertions(+), 64 deletions(-) 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() + +