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
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user