feat(mcp): implement double-insurance global-only MCP and testing rules installation
- Install MCP configs and Artemis mobile testing rules strictly into user home directories (~), avoiding project workspace pollution - Add double-insurance rules synchronization injecting into both primary global instruction files (e.g. ~/.gemini/GEMINI.md, ~/.claude/CLAUDE.md, ~/.cursorrules, ~/.codeium/windsurf/memories/global_rules.md) and modular rules/ directories - Support JSONC parsing (comments and trailing commas) and automatic .bak backups when merging existing IDE configuration files - Update README and Quick Start documentation to clarify automatic global MCP & rule mounting across all major AI IDEs
This commit is contained in:
1 parent
9e5aee85c8
commit
ec5010220b
8 files changed
+303
-33
No files matched your search
+11
@@ -56,6 +56,16 @@ dmypy.json
|
||||
# IDEs & Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
.cursor/
|
||||
.gemini/
|
||||
.claude/
|
||||
.windsurf/
|
||||
.cline/
|
||||
.roo/
|
||||
.openclaw/
|
||||
.clinerules
|
||||
.windsurfrules
|
||||
.roorules
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
@@ -80,6 +90,7 @@ scratch/
|
||||
data_engine.db
|
||||
data_engine.db-journal
|
||||
.ipc_port
|
||||
.artemis_ipc_port
|
||||
.ls_address
|
||||
.jetski_ls_address
|
||||
|
||||
|
||||
@@ -80,17 +80,19 @@ Experience seamless collaboration between **Antigravity** and **ARTEMIS** via na
|
||||
<a id="quick-start"></a>
|
||||
## ⚡ Quick Start
|
||||
|
||||
Ensure an Android device (with **USB Debugging** enabled) or emulator is connected.
|
||||
Ensure an Android device (with **USB Debugging** enabled) or emulator is connected. The one-click startup script will automatically:
|
||||
- 🛠️ **Install System Toolchains**: Detect and auto-install ADB, scrcpy, FFmpeg, and Python (`uv`) dependencies.
|
||||
- 🔌 **Mount Global MCP Server & AI Agent Rules**: Prompt to automatically install global MCP configurations and the **Artemis Mobile Testing Mindset (`rules.md`)** into your AI IDEs (**Antigravity**, **Cursor**, **Claude Code**, **Windsurf**, **VS Code**, **Cline/Roo**, **OpenClaw**).
|
||||
|
||||
```bash
|
||||
# 1. Clone repo & navigate to directory
|
||||
git clone https://github.com/google/artemis.git && cd artemis
|
||||
|
||||
# 2. One-click launch (automatically installs ADB, scrcpy, FFmpeg, uv runtime, and opens console)
|
||||
# 2. One-click launch
|
||||
# 🍎 macOS & 🐧 Linux
|
||||
./start.sh
|
||||
|
||||
# 🪟 Windows (CMD / PowerShell)
|
||||
# 🪟 Windows
|
||||
start.bat
|
||||
```
|
||||
|
||||
@@ -107,10 +109,10 @@ ARTEMIS includes a native **Model Context Protocol (MCP)** server. Connect your
|
||||
|
||||
### 1. One-Click Auto Install (Recommended)
|
||||
|
||||
Running `./start.sh` or `start.bat` will prompt you to configure MCP for detected IDEs (or you can install/update anytime later manually using the commands below):
|
||||
Running `./start.sh` or `start.bat` will prompt you to configure global MCP and testing rules for detected IDEs (or you can install/update anytime later manually using the commands below):
|
||||
|
||||
```bash
|
||||
# Auto-install for Antigravity / Jetski:
|
||||
# Auto-install MCP server & global rules for Antigravity / Jetski:
|
||||
uv run artemis mcp --install antigravity
|
||||
|
||||
# Or install for all detected AI IDEs (Antigravity, Cursor, Claude Desktop/Code, OpenClaw):
|
||||
|
||||
+7
-5
@@ -82,17 +82,19 @@
|
||||
<a id="快速上手"></a>
|
||||
## ⚡ 快速上手
|
||||
|
||||
确保电脑已连接 Android 实体机(已开启 **USB 调试**)或 Android 模拟器。
|
||||
确保电脑已连接 Android 实体机(已开启 **USB 调试**)或 Android 模拟器。一键启动脚本将会自动完成以下配置:
|
||||
- 🛠️ **安装系统环境依赖**:自动检测并安装 ADB、scrcpy、FFmpeg 与 Python(`uv`)运行时及项目依赖。
|
||||
- 🔌 **全局挂载 MCP 服务与测试准则 (Rules)**:主动引导并自动将全局 MCP 服务与 **Artemis 移动端测试思维准则 (`rules.md`)** 挂载至你使用的 AI IDE(支持 **Antigravity**、**Cursor**、**Claude Code**、**Windsurf**、**VS Code**、**Cline/Roo**、**OpenClaw**)。
|
||||
|
||||
```bash
|
||||
# 1. 克隆代码仓库并进入目录
|
||||
git clone https://github.com/google/artemis.git && cd artemis
|
||||
|
||||
# 2. 一键启动(自动检测并安装 ADB、scrcpy、FFmpeg、uv 运行时等全部依赖,并拉起控制台)
|
||||
# 2. 一键启动
|
||||
# 🍎 macOS & 🐧 Linux
|
||||
./start.sh
|
||||
|
||||
# 🪟 Windows (CMD / PowerShell)
|
||||
# 🪟 Windows
|
||||
start.bat
|
||||
```
|
||||
|
||||
@@ -109,10 +111,10 @@ ARTEMIS 内置原生 **Model Context Protocol (MCP)** 服务。只需将以下
|
||||
|
||||
### 1. 一键自动安装到 IDE(推荐)
|
||||
|
||||
运行 `./start.sh` 或 `start.bat` 启动脚本时会主动询问是否需要配置当前检测到的 AI IDE(支持跳过并在之后随时手动执行以下命令挂载):
|
||||
运行 `./start.sh` 或 `start.bat` 启动脚本时会主动询问是否自动挂载全局 MCP 与测试行为准则(支持跳过并在之后随时手动执行以下命令挂载):
|
||||
|
||||
```bash
|
||||
# 一键安装到 Antigravity / Jetski:
|
||||
# 一键安装全局 MCP 服务与 Rules 到 Antigravity / Jetski:
|
||||
uv run artemis mcp --install antigravity
|
||||
|
||||
# 或一键安装到所有检测到的 AI IDE(Antigravity、Cursor、Claude Desktop/Code、OpenClaw):
|
||||
|
||||
@@ -76,18 +76,43 @@ def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict
|
||||
return {"mcpServers": {"artemis": config_body}}
|
||||
|
||||
|
||||
def _parse_json_lenient(text: str) -> dict:
|
||||
"""Parses JSON or JSONC (JSON with comments / trailing commas) safely."""
|
||||
try:
|
||||
data = json.loads(text)
|
||||
return data if isinstance(data, dict) else {}
|
||||
except Exception:
|
||||
pass
|
||||
import re
|
||||
# Remove // single-line comments
|
||||
cleaned = re.sub(r"//.*", "", text)
|
||||
# Remove /* ... */ multi-line comments
|
||||
cleaned = re.sub(r"/\*.*?\*/", "", cleaned, flags=re.DOTALL)
|
||||
# Remove trailing commas before } or ]
|
||||
cleaned = re.sub(r",\s*([}\]])", r"\1", cleaned)
|
||||
try:
|
||||
data = json.loads(cleaned)
|
||||
return data if isinstance(data, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
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."""
|
||||
try:
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
data: dict = {}
|
||||
if file_path.exists() and file_path.stat().st_size > 0:
|
||||
raw_text = file_path.read_text(encoding="utf-8")
|
||||
data = _parse_json_lenient(raw_text)
|
||||
if not data and raw_text.strip():
|
||||
# Backup unparseable existing file to prevent accidental loss
|
||||
backup_path = file_path.with_suffix(file_path.suffix + ".bak")
|
||||
try:
|
||||
data = json.loads(file_path.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
backup_path.write_text(raw_text, encoding="utf-8")
|
||||
logger.warning(f"Existing JSON in {file_path} could not be parsed; backup created at {backup_path}")
|
||||
except Exception:
|
||||
data = {}
|
||||
pass
|
||||
|
||||
if key_name not in data or not isinstance(data.get(key_name), dict):
|
||||
data[key_name] = {}
|
||||
@@ -100,8 +125,168 @@ def _merge_json_file(file_path: Path, server_name: str, server_config: dict, key
|
||||
return False
|
||||
|
||||
|
||||
def _inject_rules_block(file_path: Path, content: str) -> bool:
|
||||
"""Injects or updates a managed ARTEMIS block in a global rules file without overwriting user content."""
|
||||
try:
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
begin_marker = "<!-- BEGIN ARTEMIS MOBILE TESTING RULES -->"
|
||||
end_marker = "<!-- END ARTEMIS MOBILE TESTING RULES -->"
|
||||
new_block = f"{begin_marker}\n{content.strip()}\n{end_marker}\n"
|
||||
|
||||
existing = ""
|
||||
if file_path.exists():
|
||||
try:
|
||||
existing = file_path.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
existing = ""
|
||||
|
||||
if begin_marker in existing and end_marker in existing:
|
||||
prefix = existing.split(begin_marker)[0]
|
||||
suffix = existing.split(end_marker)[1]
|
||||
updated = f"{prefix.rstrip()}\n\n{new_block}\n{suffix.lstrip()}".strip() + "\n"
|
||||
else:
|
||||
updated = f"{existing.rstrip()}\n\n{new_block}".strip() + "\n"
|
||||
|
||||
if existing == updated:
|
||||
return True
|
||||
|
||||
file_path.write_text(updated, encoding="utf-8")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not inject rules block into {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _write_rule_file(file_path: Path, content: str) -> bool:
|
||||
"""Writes or overwrites a standalone rule file."""
|
||||
try:
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
new_text = content.strip() + "\n"
|
||||
if file_path.exists():
|
||||
try:
|
||||
if file_path.read_text(encoding="utf-8") == new_text:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
file_path.write_text(new_text, encoding="utf-8")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not write rule file {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _write_cursor_mdc(file_path: Path, content: str) -> bool:
|
||||
"""Writes a Cursor .mdc rule file with required YAML frontmatter."""
|
||||
try:
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
mdc_header = (
|
||||
"---\n"
|
||||
"description: Artemis Mobile Testing Mindset & Rules\n"
|
||||
"globs: **/*\n"
|
||||
"alwaysApply: true\n"
|
||||
"---\n\n"
|
||||
)
|
||||
new_text = mdc_header + content.strip() + "\n"
|
||||
if file_path.exists():
|
||||
try:
|
||||
if file_path.read_text(encoding="utf-8") == new_text:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
file_path.write_text(new_text, encoding="utf-8")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not write Cursor rule file {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def install_rules(client: str, project_root: str) -> list[str]:
|
||||
"""Auto-installs ARTEMIS testing rules globally into user IDE rule directories across any OS."""
|
||||
installed_paths: list[str] = []
|
||||
rules_src = Path(project_root) / "mcp_server" / "rules.md"
|
||||
if not rules_src.exists():
|
||||
alt_src = Path(__file__).resolve().parents[4] / "mcp_server" / "rules.md"
|
||||
if alt_src.exists():
|
||||
rules_src = alt_src
|
||||
else:
|
||||
logger.warning(f"Could not locate rules.md at {rules_src}")
|
||||
return []
|
||||
|
||||
try:
|
||||
raw_rules = rules_src.read_text(encoding="utf-8").strip()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not read rules file {rules_src}: {e}")
|
||||
return []
|
||||
|
||||
targets = (
|
||||
["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw"]
|
||||
if client == "all"
|
||||
else [client]
|
||||
)
|
||||
|
||||
for target in targets:
|
||||
if target in ("antigravity", "jetski"):
|
||||
gemini_md = Path.home() / ".gemini" / "GEMINI.md"
|
||||
global_rule = Path.home() / ".gemini" / "rules" / "artemis.md"
|
||||
if _inject_rules_block(gemini_md, raw_rules):
|
||||
installed_paths.append(str(gemini_md))
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
elif target == "cursor":
|
||||
cursor_rules_file = Path.home() / ".cursorrules"
|
||||
global_mdc = Path.home() / ".cursor" / "rules" / "artemis.mdc"
|
||||
if _inject_rules_block(cursor_rules_file, raw_rules):
|
||||
installed_paths.append(str(cursor_rules_file))
|
||||
if _write_cursor_mdc(global_mdc, raw_rules):
|
||||
installed_paths.append(str(global_mdc))
|
||||
elif target in ("claude", "claude_code", "claude_desktop"):
|
||||
claude_md = Path.home() / ".claude" / "CLAUDE.md"
|
||||
global_rule = Path.home() / ".claude" / "rules" / "artemis.md"
|
||||
if _inject_rules_block(claude_md, raw_rules):
|
||||
installed_paths.append(str(claude_md))
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
elif target == "windsurf":
|
||||
global_rule = Path.home() / ".codeium" / "windsurf" / "rules" / "artemis.md"
|
||||
global_mem = Path.home() / ".codeium" / "windsurf" / "memories" / "global_rules.md"
|
||||
if _inject_rules_block(global_mem, raw_rules):
|
||||
installed_paths.append(str(global_mem))
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
elif target == "vscode":
|
||||
global_rule = Path.home() / ".vscode" / "rules" / "artemis.md"
|
||||
user_rule = _get_vscode_user_dir() / "rules" / "artemis.md"
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
if _write_rule_file(user_rule, raw_rules):
|
||||
installed_paths.append(str(user_rule))
|
||||
elif target == "cline":
|
||||
clinerules = Path.home() / ".clinerules"
|
||||
global_rule = Path.home() / ".cline" / "rules" / "artemis.md"
|
||||
if _inject_rules_block(clinerules, raw_rules):
|
||||
installed_paths.append(str(clinerules))
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
elif target in ("roo", "roo_code"):
|
||||
roorules = Path.home() / ".roorules"
|
||||
global_rule = Path.home() / ".roo" / "rules" / "artemis.md"
|
||||
if _inject_rules_block(roorules, raw_rules):
|
||||
installed_paths.append(str(roorules))
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
elif target == "openclaw":
|
||||
openclaw_md = Path.home() / ".openclaw" / "OPENCLAW.md"
|
||||
global_rule = Path.home() / ".openclaw" / "rules" / "artemis.md"
|
||||
if _inject_rules_block(openclaw_md, raw_rules):
|
||||
installed_paths.append(str(openclaw_md))
|
||||
if _write_rule_file(global_rule, raw_rules):
|
||||
installed_paths.append(str(global_rule))
|
||||
|
||||
return installed_paths
|
||||
|
||||
|
||||
def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[str]:
|
||||
"""Auto-installs/merges ARTEMIS MCP configuration into IDE config files across any OS."""
|
||||
"""Auto-installs/merges ARTEMIS MCP configuration and testing rules into IDE config files across any OS."""
|
||||
installed_paths: list[str] = []
|
||||
targets = (
|
||||
["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw"]
|
||||
@@ -166,7 +351,14 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[
|
||||
if _merge_json_file(openclaw_path, "artemis_mcp", plugin_cfg, key_name="plugins"):
|
||||
installed_paths.append(str(openclaw_path))
|
||||
|
||||
return installed_paths
|
||||
rules_paths = install_rules(client, project_root)
|
||||
installed_paths.extend(rules_paths)
|
||||
|
||||
unique_paths: list[str] = []
|
||||
for p in installed_paths:
|
||||
if p not in unique_paths:
|
||||
unique_paths.append(p)
|
||||
return unique_paths
|
||||
|
||||
|
||||
def mcp_command(
|
||||
@@ -205,7 +397,7 @@ def mcp_command(
|
||||
typer.Option(
|
||||
"--install",
|
||||
"-i",
|
||||
help="Auto-install and merge ARTEMIS MCP configuration into 'antigravity', 'claude', 'cursor', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.",
|
||||
help="Auto-install and merge ARTEMIS MCP configuration and testing rules into 'antigravity', 'claude', 'cursor', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.",
|
||||
),
|
||||
] = None,
|
||||
generate_config: Annotated[
|
||||
@@ -243,7 +435,7 @@ def mcp_command(
|
||||
)
|
||||
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]")
|
||||
console.print("[bold green]✔ Successfully installed ARTEMIS MCP server configuration & rules 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]")
|
||||
|
||||
+11
-5
@@ -41,11 +41,17 @@ The included [`rules.md`](./rules.md) file contains the **Mobile Testing Mindset
|
||||
3. **Latency & Timing Compensation**: Clarifies the difference between AI exploratory latency (e.g., model decision intervals) and the deterministic timing requirements of final test code.
|
||||
4. **"Dynamic-First, Coordinate-Fallback" Locator Pattern**: Teaches the AI to prioritize dynamic UI locators (Resource IDs, OCR text, semantics) for layout resilience, while implementing absolute coordinate fallbacks for maximum execution reliability.
|
||||
|
||||
### How to Apply `rules.md` in Your IDE
|
||||
* **Antigravity**: Add or copy the contents of `rules.md` into your Workspace Rules, Global Rules settings, or agent instructions.
|
||||
* **Claude Code**: Copy or include the contents of `rules.md` in your project's `CLAUDE.md` file (or reference it directly in your instructions).
|
||||
* **Cursor**: Copy the contents of `rules.md` into your `.cursorrules` or create a new rule file in `.cursor/rules/artemis.mdc`.
|
||||
* **Windsurf / OpenClaw**: Add the rules to your global/workspace rules or agent system prompts so the assistant always follows verified mobile testing principles.
|
||||
### How Global Rules & MCP are Installed
|
||||
When you run `uv run artemis mcp --install all` (or target a specific IDE like `--install cursor`, `--install claude`, etc.), the installer **automatically synchronizes both global MCP server configuration and global testing rules** into the corresponding user home directories (without creating project-level rule files in your workspace):
|
||||
* **Antigravity / Jetski**: Automatically installs global rules to `~/.gemini/rules/artemis.md`.
|
||||
* **Cursor**: Automatically installs global YAML-frontmatter rule file at `~/.cursor/rules/artemis.mdc`.
|
||||
* **Claude Code / Desktop**: Automatically installs global rules to `~/.claude/rules/artemis.md`.
|
||||
* **Windsurf**: Automatically installs global rules to `~/.codeium/windsurf/rules/artemis.md`.
|
||||
* **VS Code**: Automatically installs global rules to `~/.vscode/rules/artemis.md`.
|
||||
* **Cline / Roo Code**: Automatically installs global rules to `~/.cline/rules/artemis.md` and `~/.roo/rules/artemis.md`.
|
||||
* **OpenClaw**: Automatically installs global rules to `~/.openclaw/rules/artemis.md`.
|
||||
|
||||
*(Note: If you prefer manual configuration, you can also copy the contents of [`rules.md`](./rules.md) into your IDE's system prompt or global rules settings.)*
|
||||
|
||||
## 🛠️ MCP Tools Overview
|
||||
|
||||
|
||||
+3
-3
@@ -132,11 +132,11 @@ if ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Path $ShowcaseIndexAlt1))
|
||||
|
||||
# 7. Optionally configure MCP for detected AI IDEs
|
||||
Write-Host ""
|
||||
Write-Host " 🔌 Would you like to configure ARTEMIS MCP for your AI IDEs?" -ForegroundColor Cyan
|
||||
Write-Host " 🔌 Would you like to configure ARTEMIS MCP & testing rules for your AI IDEs?" -ForegroundColor Cyan
|
||||
Write-Host " (Supported: Antigravity, Cursor, Claude Code/Desktop, OpenClaw, Windsurf)"
|
||||
$installMcp = "Y"
|
||||
if ([Console]::IsInputRedirected -eq $false) {
|
||||
$response = Read-Host " Install MCP configuration now? [Y/n]"
|
||||
$response = Read-Host " Install MCP configuration & rules now? [Y/n]"
|
||||
if ($response -ne "") {
|
||||
$installMcp = $response
|
||||
}
|
||||
@@ -145,7 +145,7 @@ if ([Console]::IsInputRedirected -eq $false) {
|
||||
}
|
||||
|
||||
if ($installMcp -match "^[Yy]") {
|
||||
Write-Host " ✔ Installing MCP server configuration..." -ForegroundColor Green
|
||||
Write-Host " ✔ Installing MCP server configuration & testing rules..." -ForegroundColor Green
|
||||
uv run artemis mcp --install all 2>$null | Out-Null
|
||||
Write-Host " 💡 Tip: You can update or re-install anytime with: uv run artemis mcp --install all" -ForegroundColor Cyan
|
||||
} else {
|
||||
|
||||
@@ -155,17 +155,17 @@ fi
|
||||
|
||||
# 7. Optionally configure MCP for detected AI IDEs
|
||||
echo ""
|
||||
echo -e " ${CYAN}🔌 Would you like to configure ARTEMIS MCP for your AI IDEs?${NC}"
|
||||
echo -e " ${CYAN}🔌 Would you like to configure ARTEMIS MCP & testing rules for your AI IDEs?${NC}"
|
||||
echo -e " (Supported: Antigravity, Cursor, Claude Code/Desktop, OpenClaw, Windsurf)"
|
||||
if [ -t 0 ]; then
|
||||
read -r -p " Install MCP configuration now? [Y/n]: " INSTALL_MCP
|
||||
read -r -p " Install MCP configuration & rules now? [Y/n]: " INSTALL_MCP
|
||||
INSTALL_MCP=${INSTALL_MCP:-Y}
|
||||
else
|
||||
INSTALL_MCP="n"
|
||||
fi
|
||||
|
||||
if [[ "${INSTALL_MCP}" =~ ^[Yy]$ ]]; then
|
||||
echo -e " ${GREEN}✔ Installing MCP server configuration...${NC}"
|
||||
echo -e " ${GREEN}✔ Installing MCP server configuration & testing rules...${NC}"
|
||||
uv run artemis mcp --install all >/dev/null 2>&1 || true
|
||||
echo -e " ${CYAN}💡 Tip: You can update or re-install anytime with: ${BOLD}uv run artemis mcp --install all${NC}"
|
||||
else
|
||||
|
||||
+61
-4
@@ -103,11 +103,12 @@ def test_cli_mcp_generate_config_all():
|
||||
|
||||
|
||||
def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
|
||||
"""Verify 'artemis mcp --install antigravity' writes configuration into target file."""
|
||||
"""Verify 'artemis mcp --install antigravity' writes configuration and global rules into target files."""
|
||||
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
||||
monkeypatch.setattr("mcp_server.utils.env_utils.get_project_root", lambda: str(tmp_path))
|
||||
result = runner.invoke(app, ["mcp", "--install", "antigravity"])
|
||||
assert result.exit_code == 0
|
||||
assert "Successfully installed ARTEMIS MCP server configuration" in result.output
|
||||
assert "Successfully installed ARTEMIS MCP server configuration & rules" in result.output
|
||||
jetski_file = tmp_path / ".gemini" / "jetski" / "mcp_config.json"
|
||||
assert jetski_file.exists()
|
||||
import json
|
||||
@@ -116,16 +117,72 @@ def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
|
||||
assert "mobile_run_task" in data["mcpServers"]["artemis"]["tools"]
|
||||
assert "PYTHONPATH" in data["mcpServers"]["artemis"]["env"]
|
||||
|
||||
# Verify global rule file installed
|
||||
gemini_md = tmp_path / ".gemini" / "GEMINI.md"
|
||||
assert gemini_md.exists()
|
||||
assert "Mobile Testing Mindset" in gemini_md.read_text(encoding="utf-8")
|
||||
rule_file = tmp_path / ".gemini" / "rules" / "artemis.md"
|
||||
assert rule_file.exists()
|
||||
assert "Mobile Testing Mindset" in rule_file.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_cli_mcp_install_all(tmp_path, monkeypatch):
|
||||
"""Verify 'artemis mcp --install all' installs to all supported IDE locations."""
|
||||
"""Verify 'artemis mcp --install all' installs configs and global rules to all supported IDE locations."""
|
||||
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
||||
monkeypatch.setattr("mcp_server.utils.env_utils.get_project_root", lambda: str(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 "Successfully installed ARTEMIS MCP server configuration & rules" 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()
|
||||
assert (tmp_path / ".claude.json").exists()
|
||||
|
||||
# Verify global rule files installed across IDEs
|
||||
assert (tmp_path / ".gemini" / "GEMINI.md").exists()
|
||||
assert (tmp_path / ".gemini" / "rules" / "artemis.md").exists()
|
||||
assert (tmp_path / ".cursorrules").exists()
|
||||
assert (tmp_path / ".cursor" / "rules" / "artemis.mdc").exists()
|
||||
assert (tmp_path / ".claude" / "CLAUDE.md").exists()
|
||||
assert (tmp_path / ".claude" / "rules" / "artemis.md").exists()
|
||||
assert (tmp_path / ".codeium" / "windsurf" / "memories" / "global_rules.md").exists()
|
||||
assert (tmp_path / ".codeium" / "windsurf" / "rules" / "artemis.md").exists()
|
||||
assert (tmp_path / ".vscode" / "rules" / "artemis.md").exists()
|
||||
assert (tmp_path / ".clinerules").exists()
|
||||
assert (tmp_path / ".cline" / "rules" / "artemis.md").exists()
|
||||
assert (tmp_path / ".roorules").exists()
|
||||
assert (tmp_path / ".roo" / "rules" / "artemis.md").exists()
|
||||
assert (tmp_path / ".openclaw" / "OPENCLAW.md").exists()
|
||||
assert (tmp_path / ".openclaw" / "rules" / "artemis.md").exists()
|
||||
|
||||
|
||||
def test_cli_mcp_install_jsonc_and_backup(tmp_path, monkeypatch):
|
||||
"""Verify merging configuration preserves valid JSONC and creates .bak for unparseable JSON."""
|
||||
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
||||
monkeypatch.setattr("mcp_server.utils.env_utils.get_project_root", lambda: str(tmp_path))
|
||||
|
||||
cursor_dir = tmp_path / ".cursor"
|
||||
cursor_dir.mkdir(parents=True, exist_ok=True)
|
||||
mcp_json = cursor_dir / "mcp.json"
|
||||
|
||||
# 1. Valid JSONC with comments and trailing comma
|
||||
mcp_json.write_text(
|
||||
'{\n // My existing server\n "mcpServers": {\n "test": {"command": "echo"},\n }\n}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = runner.invoke(app, ["mcp", "--install", "cursor"])
|
||||
assert result.exit_code == 0
|
||||
import json
|
||||
data = json.loads(mcp_json.read_text(encoding="utf-8"))
|
||||
assert "test" in data["mcpServers"]
|
||||
assert "artemis" in data["mcpServers"]
|
||||
|
||||
# 2. Corrupt / unparseable file triggers backup
|
||||
mcp_json.write_text("INVALID JSON DATA {{{{", encoding="utf-8")
|
||||
result2 = runner.invoke(app, ["mcp", "--install", "cursor"])
|
||||
assert result2.exit_code == 0
|
||||
backup_file = cursor_dir / "mcp.json.bak"
|
||||
assert backup_file.exists()
|
||||
assert "INVALID JSON DATA" in backup_file.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
Reference in new issue
Block a user