diff --git a/README.md b/README.md
index c26406a..a0d256f 100644
--- a/README.md
+++ b/README.md
@@ -82,26 +82,37 @@ Experience seamless collaboration between **Antigravity** and **ARTEMIS** via na
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**).
+- 🔌 **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**, **Codex**, **Windsurf**, **VS Code**, **Cline/Roo**, **OpenClaw**).
+
+### 🍎 macOS and 🐧 Linux
```bash
# 1. Clone repo & navigate to directory
git clone https://github.com/google/artemis.git && cd artemis
# 2. One-click launch
-# 🍎 macOS & 🐧 Linux
./start.sh
-
-# 🪟 Windows
-start.bat
```
+### 🪟 Windows PowerShell
+
+```powershell
+# 1. Clone repo & navigate to directory
+git clone https://github.com/google/artemis.git
+cd artemis
+
+# 2. One-click launch
+.\start.bat
+```
+
+> PowerShell does not search the current directory for executable scripts by default, so use `.\start.bat` without a trailing `\`. In Command Prompt (CMD), use `start.bat` instead.
+
> 💡 **Tip**: Opens `http://localhost:8000` in your default browser with a device connection wizard, live screen mirroring, prompt sandbox, and execution replays. You can also run directly from CLI: `uv run artemis run "Open Settings, find Battery and tell me current level" --profile flash`.
-🔌 MCP Setup for Antigravity / Claude Code / Windsurf (Click to expand)
+🔌 MCP Setup for Codex / Antigravity / Claude Code / Windsurf (Click to expand)
@@ -109,13 +120,13 @@ 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 global MCP and testing rules for detected IDEs (or you can install/update anytime later manually using the commands below):
+Running `./start.sh` (macOS/Linux) or `.\start.bat` (Windows PowerShell) 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 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):
+# Or install for all supported AI IDEs (including Codex):
uv run artemis mcp --install all
```
@@ -124,7 +135,19 @@ uv run artemis mcp --install all
### 2. Manual Configuration (Optional)
-If you prefer to configure manually, run `uv 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):
+If you prefer to configure manually, run `uv run artemis mcp --generate-config ` (for example, `codex` or `antigravity`) to output the appropriate TOML or JSON snippet. Replace `/path/to/artemis` with your actual repo path and point `command` to your `.venv` Python executable:
+
+* **Codex** (`~/.codex/config.toml`):
+```toml
+[mcp_servers.artemis]
+command = "/path/to/artemis/.venv/bin/python"
+args = ["-m", "mcp_server"]
+cwd = "/path/to/artemis"
+
+[mcp_servers.artemis.env]
+PYTHONUNBUFFERED = "1"
+PYTHONPATH = "/path/to/artemis"
+```
* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
```json
@@ -169,12 +192,13 @@ You can mount or copy [`mcp_server/rules.md`](./mcp_server/rules.md) into your A
* **Antigravity**: Add the contents of `rules.md` to 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.
* **Cursor**: Copy the contents into `.cursorrules` or create a rule file at `.cursor/rules/artemis.mdc`.
+* **Codex**: Add the contents to `~/.codex/AGENTS.md` (or the active `AGENTS.override.md`).
* **Windsurf / OpenClaw**: Add the rules to your workspace rules or global system prompts.
> 💡 For more details on the testing mindset and MCP architecture, see the [MCP Server README](./mcp_server/README.md).
### 4. Prompt Your Phone in the IDE Chat
-In Antigravity or Claude Code, simply prompt:
+In Codex, Antigravity, or Claude Code, simply prompt:
> 💬 *"Build the latest changes into an APK, install it on the connected device, open the login screen with a test account, verify if there are any unexpected popups after login, and return screenshots of the final page."*
diff --git a/README_CN.md b/README_CN.md
index 0ee438e..f7a61be 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -84,26 +84,37 @@
确保电脑已连接 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**)。
+- 🔌 **全局挂载 MCP 服务与测试准则 (Rules)**:主动引导并自动将全局 MCP 服务与 **Artemis 移动端测试思维准则 (`rules.md`)** 挂载至你使用的 AI IDE(支持 **Antigravity**、**Cursor**、**Claude Code**、**Codex**、**Windsurf**、**VS Code**、**Cline/Roo**、**OpenClaw**)。
+
+### 🍎 macOS 与 🐧 Linux
```bash
# 1. 克隆代码仓库并进入目录
git clone https://github.com/google/artemis.git && cd artemis
# 2. 一键启动
-# 🍎 macOS & 🐧 Linux
./start.sh
-
-# 🪟 Windows
-start.bat
```
+### 🪟 Windows PowerShell
+
+```powershell
+# 1. 克隆代码仓库并进入目录
+git clone https://github.com/google/artemis.git
+cd artemis
+
+# 2. 一键启动
+.\start.bat
+```
+
+> PowerShell 默认不会从当前目录查找可执行脚本,因此必须使用 `.\start.bat`,且命令末尾不要添加 `\`。如果使用传统命令提示符(CMD),则运行 `start.bat`。
+
> 💡 **提示**:启动后将自动在默认浏览器中打开 Web 控制台(`http://localhost:8000`),提供设备连接向导、实时投屏、任务演练与状态回放面板。你也可以通过命令行直接运行:`uv run artemis run "打开系统设置,找到电池选项并告诉我当前电量" --profile flash`。
-🔌 接入 Antigravity / Claude Code / Windsurf (MCP)(点击展开)
+🔌 接入 Codex / Antigravity / Claude Code / Windsurf (MCP)(点击展开)
@@ -111,13 +122,13 @@ ARTEMIS 内置原生 **Model Context Protocol (MCP)** 服务。只需将以下
### 1. 一键自动安装到 IDE(推荐)
-运行 `./start.sh` 或 `start.bat` 启动脚本时会主动询问是否自动挂载全局 MCP 与测试行为准则(支持跳过并在之后随时手动执行以下命令挂载):
+运行 `./start.sh`(macOS/Linux)或 `.\start.bat`(Windows PowerShell)启动脚本时,会主动询问是否自动挂载全局 MCP 与测试行为准则(支持跳过并在之后随时手动执行以下命令挂载):
```bash
# 一键安装全局 MCP 服务与 Rules 到 Antigravity / Jetski:
uv run artemis mcp --install antigravity
-# 或一键安装到所有检测到的 AI IDE(Antigravity、Cursor、Claude Desktop/Code、OpenClaw):
+# 或一键安装到所有支持的 AI IDE(包括 Codex):
uv run artemis mcp --install all
```
@@ -126,7 +137,19 @@ uv run artemis mcp --install all
### 2. 手动配置(可选)
-如果你习惯手动复制配置,可运行 `uv run artemis mcp --generate-config antigravity`(或 `all`)获取自动计算好路径的 JSON 代码段,也可以参考以下模板将 `command` 填写为项目下 `.venv` 虚拟环境中的 Python 绝对路径,并将 `/path/to/artemis` 替换为项目实际路径:
+如果你习惯手动复制配置,可运行 `uv run artemis mcp --generate-config `(例如 `codex` 或 `antigravity`)获取对应的 TOML 或 JSON 配置。请将 `command` 填写为项目下 `.venv` 虚拟环境中的 Python 绝对路径,并将 `/path/to/artemis` 替换为项目实际路径:
+
+* **Codex** (`~/.codex/config.toml`):
+```toml
+[mcp_servers.artemis]
+command = "/path/to/artemis/.venv/bin/python"
+args = ["-m", "mcp_server"]
+cwd = "/path/to/artemis"
+
+[mcp_servers.artemis.env]
+PYTHONUNBUFFERED = "1"
+PYTHONPATH = "/path/to/artemis"
+```
* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
```json
@@ -171,12 +194,13 @@ uv run artemis mcp --install all
* **Antigravity**:将 `rules.md` 内容添加至工作区规则(Workspace Rules)或全局规则设置中。
* **Claude Code**:将 `rules.md` 内容复制或引入至项目根目录的 `CLAUDE.md` 文件中。
* **Cursor**:将内容复制到 `.cursorrules` 文件或在 `.cursor/rules/artemis.mdc` 中创建新规则。
+* **Codex**:将内容添加至 `~/.codex/AGENTS.md`(或当前生效的 `AGENTS.override.md`)。
* **Windsurf / OpenClaw**:将内容添加到工作区规则或全局 System Prompt 中。
> 💡 更多规范设计细节与 MCP 架构说明,请参阅 [MCP Server 文档](./mcp_server/README.md)。
### 4. 在 IDE 中体验真机协同
-在 Antigravity / Claude Code 对话框中直接输入:
+在 Codex / Antigravity / Claude Code 对话框中直接输入:
> 💬 *"请帮我把刚刚修改的代码编译成 APK 并安装到手机上,打开登录页面输入测试账号,验证登录后是否有异常弹窗,并把最终页面截图回传。"*
diff --git a/apps/showcase_ui/src/app/pages/home/home.component.scss b/apps/showcase_ui/src/app/pages/home/home.component.scss
index 424a4df..69ff4fb 100644
--- a/apps/showcase_ui/src/app/pages/home/home.component.scss
+++ b/apps/showcase_ui/src/app/pages/home/home.component.scss
@@ -54,6 +54,51 @@ $text-secondary: #475569;
$text-muted: #64748b;
$text-light: #94a3b8;
+// Shared OS switcher. It is rendered in both the dependency installer and
+// the empty-AVD guide, so the styling cannot be scoped to either card.
+.os-selector-pills {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px;
+ gap: 2px;
+ background: rgba(255, 255, 255, 0.65);
+ border: 1px solid rgba(226, 232, 240, 0.85);
+ border-radius: 8px;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), inset 0 1px 1px rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(12px) saturate(180%);
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
+
+ .os-pill {
+ padding: 3px 9px;
+ border: 0;
+ border-radius: 6px;
+ background: transparent;
+ color: $text-secondary;
+ font-family: inherit;
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 1.35;
+ cursor: pointer;
+ transition: color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
+
+ &:hover {
+ color: $text-main;
+ background: rgba(255, 255, 255, 0.72);
+ }
+
+ &.active {
+ color: #ffffff;
+ background: $primary;
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
+ }
+
+ &:focus-visible {
+ outline: 2px solid rgba($primary, 0.35);
+ outline-offset: 1px;
+ }
+ }
+}
+
// =============================================================================
// FROSTED GLASS (GLASSMORPHISM) MIXINS
// =============================================================================
diff --git a/artemis/interfaces/cli/commands/init.py b/artemis/interfaces/cli/commands/init.py
index c73b59d..c8fa063 100644
--- a/artemis/interfaces/cli/commands/init.py
+++ b/artemis/interfaces/cli/commands/init.py
@@ -141,19 +141,21 @@ def init_command() -> None:
console.print(" [3] Claude Desktop / Claude Code")
console.print(" [4] Windsurf")
console.print(" [5] VS Code / Cline / Roo Code")
- console.print(" [6] All supported IDEs")
- console.print(" [7] Skip for now")
+ console.print(" [6] Codex")
+ console.print(" [7] All supported IDEs")
+ console.print(" [8] Skip for now")
mcp_choice = Prompt.ask(
- "Select IDE for MCP auto-install", choices=["1", "2", "3", "4", "5", "6", "7"], default="1"
+ "Select IDE for MCP auto-install", choices=["1", "2", "3", "4", "5", "6", "7", "8"], default="1"
)
- if mcp_choice != "7":
+ if mcp_choice != "8":
target_map = {
"1": "antigravity",
"2": "cursor",
"3": "claude",
"4": "windsurf",
"5": "vscode",
- "6": "all",
+ "6": "codex",
+ "7": "all",
}
target = target_map.get(mcp_choice, "antigravity")
try:
diff --git a/artemis/interfaces/cli/commands/mcp.py b/artemis/interfaces/cli/commands/mcp.py
index dfe1981..3375b18 100644
--- a/artemis/interfaces/cli/commands/mcp.py
+++ b/artemis/interfaces/cli/commands/mcp.py
@@ -17,7 +17,9 @@
import json
import os
from pathlib import Path
+import re
import sys
+import tomllib
from typing import Annotated
from mcp_server.base import mcp as agent_mcp
@@ -72,6 +74,8 @@ def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict
}
elif client == "openclaw":
return {"plugins": {"artemis_mcp": {"enabled": True, "type": "mcp", "server": config_body}}}
+ elif client == "codex":
+ return {"mcp_servers": {"artemis": config_body}}
else: # cursor, windsurf, claude, vscode, cline, roo, generic
return {"mcpServers": {"artemis": config_body}}
@@ -83,7 +87,6 @@ def _parse_json_lenient(text: str) -> dict:
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
@@ -125,6 +128,76 @@ def _merge_json_file(file_path: Path, server_name: str, server_config: dict, key
return False
+def _codex_toml_block(server_config: dict) -> str:
+ """Renders a managed Codex MCP server block using TOML-compatible JSON strings."""
+ command = json.dumps(str(server_config["command"]), ensure_ascii=False)
+ args = json.dumps(server_config.get("args", []), ensure_ascii=False)
+ cwd = json.dumps(str(server_config["cwd"]), ensure_ascii=False)
+ env = server_config.get("env", {})
+ lines = [
+ "# BEGIN ARTEMIS MCP CONFIG",
+ "[mcp_servers.artemis]",
+ f"command = {command}",
+ f"args = {args}",
+ f"cwd = {cwd}",
+ ]
+ if env:
+ lines.extend(["", "[mcp_servers.artemis.env]"])
+ for key, value in env.items():
+ lines.append(f"{key} = {json.dumps(str(value), ensure_ascii=False)}")
+ lines.append("# END ARTEMIS MCP CONFIG")
+ return "\n".join(lines) + "\n"
+
+
+def _merge_codex_toml(file_path: Path, server_config: dict) -> bool:
+ """Adds or updates Artemis in Codex config.toml while preserving unrelated settings."""
+ try:
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+ existing = file_path.read_text(encoding="utf-8") if file_path.exists() else ""
+ managed_block = _codex_toml_block(server_config)
+ begin_marker = "# BEGIN ARTEMIS MCP CONFIG"
+ end_marker = "# END ARTEMIS MCP CONFIG"
+
+ if begin_marker in existing and end_marker in existing:
+ managed_pattern = re.compile(
+ rf"(?ms)^{re.escape(begin_marker)}\n.*?^{re.escape(end_marker)}\n?"
+ )
+ updated = managed_pattern.sub(managed_block, existing, count=1)
+ else:
+ # Remove an older, unmanaged Artemis table before adding the managed block.
+ kept_lines: list[str] = []
+ skipping_artemis = False
+ header_pattern = re.compile(r"^\s*\[{1,2}\s*(.+?)\s*\]{1,2}\s*(?:#.*)?$")
+ for line in existing.splitlines(keepends=True):
+ header_match = header_pattern.match(line.rstrip("\r\n"))
+ if header_match:
+ normalized = re.sub(r"[\s\"']", "", header_match.group(1)).lower()
+ skipping_artemis = normalized == "mcp_servers.artemis" or normalized.startswith(
+ "mcp_servers.artemis."
+ )
+ if not skipping_artemis:
+ kept_lines.append(line)
+ preserved = "".join(kept_lines).rstrip()
+ updated = f"{preserved}\n\n{managed_block}" if preserved else managed_block
+
+ # Never replace a user's config with invalid TOML.
+ tomllib.loads(updated)
+ if updated != existing:
+ file_path.write_text(updated, encoding="utf-8")
+ return True
+ except Exception as e:
+ if file_path.exists():
+ try:
+ backup_path = file_path.with_suffix(file_path.suffix + ".bak")
+ backup_path.write_text(file_path.read_text(encoding="utf-8"), encoding="utf-8")
+ logger.warning(f"Could not update {file_path}; backup created at {backup_path}: {e}")
+ except Exception:
+ pass
+ else:
+ logger.warning(f"Could not create Codex MCP config file {file_path}: {e}")
+ 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:
@@ -219,7 +292,7 @@ def install_rules(client: str, project_root: str) -> list[str]:
return []
targets = (
- ["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw"]
+ ["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw", "codex"]
if client == "all"
else [client]
)
@@ -281,6 +354,16 @@ def install_rules(client: str, project_root: str) -> list[str]:
installed_paths.append(str(openclaw_md))
if _write_rule_file(global_rule, raw_rules):
installed_paths.append(str(global_rule))
+ elif target == "codex":
+ codex_home = Path(os.environ.get("CODEX_HOME") or (Path.home() / ".codex"))
+ override_file = codex_home / "AGENTS.override.md"
+ agents_file = (
+ override_file
+ if override_file.exists() and override_file.read_text(encoding="utf-8").strip()
+ else codex_home / "AGENTS.md"
+ )
+ if _inject_rules_block(agents_file, raw_rules):
+ installed_paths.append(str(agents_file))
return installed_paths
@@ -289,7 +372,7 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[
"""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"]
+ ["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw", "codex"]
if client == "all"
else [client]
)
@@ -350,6 +433,12 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[
openclaw_path = Path.home() / ".openclaw" / "openclaw.json"
if _merge_json_file(openclaw_path, "artemis_mcp", plugin_cfg, key_name="plugins"):
installed_paths.append(str(openclaw_path))
+ elif target == "codex":
+ server_cfg = snippet["mcp_servers"]["artemis"]
+ codex_home = Path(os.environ.get("CODEX_HOME") or (Path.home() / ".codex"))
+ codex_path = codex_home / "config.toml"
+ if _merge_codex_toml(codex_path, server_cfg):
+ installed_paths.append(str(codex_path))
rules_paths = install_rules(client, project_root)
installed_paths.extend(rules_paths)
@@ -397,7 +486,7 @@ def mcp_command(
typer.Option(
"--install",
"-i",
- help="Auto-install and merge ARTEMIS MCP configuration and testing rules 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', 'codex', or 'all'.",
),
] = None,
generate_config: Annotated[
@@ -405,7 +494,7 @@ def mcp_command(
typer.Option(
"--generate-config",
"-g",
- help="Output ready-to-use MCP configuration JSON for 'antigravity', 'cursor', 'claude', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.",
+ help="Output ready-to-use MCP configuration for 'antigravity', 'cursor', 'claude', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', 'codex', or 'all'.",
),
] = None,
) -> None:
@@ -428,10 +517,11 @@ def mcp_command(
"roo",
"roo_code",
"openclaw",
+ "codex",
"all",
):
console.print(
- f"[bold red]Unsupported install target: '{install_config}'. Use 'antigravity', 'claude', 'cursor', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.[/bold red]"
+ f"[bold red]Unsupported install target: '{install_config}'. Use 'antigravity', 'claude', 'cursor', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', 'codex', or 'all'.[/bold red]"
)
raise typer.Exit(1)
installed_paths = install_mcp_config(client, python_exe, project_root)
@@ -469,13 +559,22 @@ def mcp_command(
"openclaw (openclaw.json)": _get_config_snippet(
"openclaw", python_exe, project_root
),
+ "codex (~/.codex/config.toml)": _get_config_snippet(
+ "codex", python_exe, project_root
+ ),
}
json_str = json.dumps(all_configs, indent=2)
+ syntax_language = "json"
+ elif client == "codex":
+ server_cfg = _get_config_snippet("codex", python_exe, project_root)["mcp_servers"]["artemis"]
+ json_str = _codex_toml_block(server_cfg)
+ syntax_language = "toml"
else:
snippet = _get_config_snippet(client, python_exe, project_root)
json_str = json.dumps(snippet, indent=2)
+ syntax_language = "json"
- syntax = Syntax(json_str, "json", theme="monokai", line_numbers=False)
+ syntax = Syntax(json_str, syntax_language, theme="monokai", line_numbers=False)
console.print(f"[bold cyan]MCP Configuration for {client.upper()}:[/bold cyan]")
console.print(syntax)
raise typer.Exit(0)
diff --git a/scripts/start.ps1 b/scripts/start.ps1
index 2041d44..39054fc 100644
--- a/scripts/start.ps1
+++ b/scripts/start.ps1
@@ -26,7 +26,7 @@ $RootDir = Split-Path -Parent $ScriptDir
Set-Location $RootDir
Write-Host "======================================================" -ForegroundColor Cyan
-Write-Host " ✨ Artemis Autonomous Mobile Agent UI " -ForegroundColor Cyan
+Write-Host " Artemis Autonomous Mobile Agent UI " -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
@@ -59,7 +59,7 @@ Update-EnvironmentPath
# 2. Check or install uv
if (-not (Test-CommandExists "uv")) {
- Write-Host "⚡ uv not found. Installing Astral uv..." -ForegroundColor Yellow
+ Write-Host "[INFO] uv not found. Installing Astral uv..." -ForegroundColor Yellow
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Update-EnvironmentPath
}
@@ -72,7 +72,7 @@ if (-not (Test-CommandExists "scrcpy")) { $missingCore += "scrcpy" }
if ($missingCore.Count -gt 0) {
if (Test-CommandExists "winget") {
- Write-Host "⚡ Auto-installing missing components ($($missingCore -join ', '))..." -ForegroundColor DarkYellow
+ Write-Host "[INFO] Auto-installing missing components ($($missingCore -join ', '))..." -ForegroundColor DarkYellow
if ($missingCore -contains "adb") {
winget install --id Google.PlatformTools -e --accept-source-agreements --accept-package-agreements --silent 2>$null | Out-Null
}
@@ -90,14 +90,14 @@ if ($missingCore.Count -gt 0) {
if (-not (Test-Path ".env")) {
if (Test-Path ".env.example") {
Copy-Item ".env.example" ".env"
- Write-Host " ✔ Initialized .env configuration file." -ForegroundColor Green
+ Write-Host " [OK] Initialized .env configuration file." -ForegroundColor Green
} else {
New-Item -ItemType File -Path ".env" | Out-Null
}
}
# 5. Synchronize dependencies
-Write-Host " 📦 Synchronizing Python runtime and project dependencies..." -ForegroundColor Cyan
+Write-Host " [INFO] Synchronizing Python runtime and project dependencies..." -ForegroundColor Cyan
uv sync --quiet
# 6. Check and build Showcase UI if not already compiled
@@ -107,33 +107,33 @@ $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
+ Write-Host " [INFO] 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
+ Write-Host " [INFO] 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
+ Write-Host " [OK] Showcase UI compiled successfully." -ForegroundColor Green
} catch {
- Write-Host " ⚠ Failed to build Showcase UI: $_" -ForegroundColor DarkYellow
+ Write-Host " [WARN] 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
+ Write-Host " [WARN] Node.js/npm not found. Please install Node.js (>= 18) to compile Showcase UI." -ForegroundColor DarkYellow
}
}
# 7. Optionally configure MCP for detected AI IDEs
Write-Host ""
-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)"
+Write-Host " [INFO] Would you like to configure ARTEMIS MCP & testing rules for your AI IDEs?" -ForegroundColor Cyan
+Write-Host " (Supported: Antigravity, Cursor, Claude Code/Desktop, Codex, OpenClaw, Windsurf)"
$installMcp = "Y"
if ([Console]::IsInputRedirected -eq $false) {
$response = Read-Host " Install MCP configuration & rules now? [Y/n]"
@@ -145,16 +145,21 @@ if ([Console]::IsInputRedirected -eq $false) {
}
if ($installMcp -match "^[Yy]") {
- 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
+ Write-Host " Installing MCP server configuration & testing rules..." -ForegroundColor Cyan
+ uv run artemis mcp --install all
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host " [OK] MCP configuration and rules installed successfully." -ForegroundColor Green
+ Write-Host " [TIP] You can update or re-install anytime with: uv run artemis mcp --install all" -ForegroundColor Cyan
+ } else {
+ Write-Host " [WARN] MCP installation failed. Review the error above and retry with: uv run artemis mcp --install all" -ForegroundColor Yellow
+ }
} else {
- Write-Host " ⏭️ Skipped. You can install MCP anytime later with: uv run artemis mcp --install all" -ForegroundColor Yellow
+ Write-Host " [SKIP] You can install MCP anytime later with: uv run artemis mcp --install all" -ForegroundColor Yellow
}
Write-Host ""
# 8. Launch unified Showcase UI & open browser
-Write-Host " 🚀 Launching Artemis Showcase UI & Admin Console..." -ForegroundColor Green
+Write-Host " [INFO] Launching Artemis Showcase UI & Admin Console..." -ForegroundColor Green
if ($NoOpen) {
uv run artemis ui --port $Port --no-open
} else {
diff --git a/start.sh b/start.sh
index 18396c2..f115bcc 100755
--- a/start.sh
+++ b/start.sh
@@ -156,7 +156,7 @@ fi
# 7. Optionally configure MCP for detected AI IDEs
echo ""
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)"
+echo -e " (Supported: Antigravity, Cursor, Claude Code/Desktop, Codex, OpenClaw, Windsurf)"
if [ -t 0 ]; then
read -r -p " Install MCP configuration & rules now? [Y/n]: " INSTALL_MCP
INSTALL_MCP=${INSTALL_MCP:-Y}
@@ -165,9 +165,13 @@ else
fi
if [[ "${INSTALL_MCP}" =~ ^[Yy]$ ]]; then
- 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}"
+ echo -e " ${CYAN}Installing MCP server configuration & testing rules...${NC}"
+ if uv run artemis mcp --install all; then
+ echo -e " ${GREEN}✔ MCP configuration and rules installed successfully.${NC}"
+ echo -e " ${CYAN}💡 Tip: You can update or re-install anytime with: ${BOLD}uv run artemis mcp --install all${NC}"
+ else
+ echo -e " ${YELLOW}⚠ MCP installation failed. Review the error above and retry with: ${BOLD}uv run artemis mcp --install all${NC}"
+ fi
else
echo -e " ${YELLOW}⏭️ Skipped. You can install MCP anytime later with: ${BOLD}uv run artemis mcp --install all${NC}"
fi
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
index e13ba12..756b444 100644
--- a/tests/unit/test_cli.py
+++ b/tests/unit/test_cli.py
@@ -90,7 +90,7 @@ def test_cli_mcp_generate_config_antigravity():
def test_cli_mcp_generate_config_all():
- """Verify 'artemis mcp --generate-config all' includes antigravity, cursor, windsurf, claude, vscode, cline, and roo."""
+ """Verify 'artemis mcp --generate-config all' includes every supported client."""
result = runner.invoke(app, ["mcp", "--generate-config", "all"])
assert result.exit_code == 0
assert "antigravity" in result.output
@@ -100,6 +100,15 @@ def test_cli_mcp_generate_config_all():
assert "vscode" in result.output
assert "cline" in result.output
assert "roo" in result.output
+ assert "codex" in result.output
+
+
+def test_cli_mcp_generate_config_codex():
+ """Verify Codex generation produces a ready-to-use TOML server block."""
+ result = runner.invoke(app, ["mcp", "--generate-config", "codex"])
+ assert result.exit_code == 0
+ assert "[mcp_servers.artemis]" in result.output
+ assert "[mcp_servers.artemis.env]" in result.output
def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
@@ -137,6 +146,7 @@ def test_cli_mcp_install_all(tmp_path, monkeypatch):
assert (tmp_path / ".codeium" / "windsurf" / "mcp_config.json").exists()
assert (tmp_path / ".openclaw" / "openclaw.json").exists()
assert (tmp_path / ".claude.json").exists()
+ assert (tmp_path / ".codex" / "config.toml").exists()
# Verify global rule files installed across IDEs
assert (tmp_path / ".gemini" / "GEMINI.md").exists()
@@ -154,6 +164,41 @@ def test_cli_mcp_install_all(tmp_path, monkeypatch):
assert (tmp_path / ".roo" / "rules" / "artemis.md").exists()
assert (tmp_path / ".openclaw" / "OPENCLAW.md").exists()
assert (tmp_path / ".openclaw" / "rules" / "artemis.md").exists()
+ assert (tmp_path / ".codex" / "AGENTS.md").exists()
+
+
+def test_cli_mcp_install_codex_preserves_config_and_is_idempotent(tmp_path, monkeypatch):
+ """Verify Codex TOML merging preserves other servers and updates only Artemis."""
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
+ monkeypatch.setattr("mcp_server.utils.env_utils.get_project_root", lambda: str(tmp_path))
+ codex_dir = tmp_path / ".codex"
+ codex_dir.mkdir(parents=True)
+ config_file = codex_dir / "config.toml"
+ config_file.write_text(
+ 'model = "test-model"\n\n'
+ '[mcp_servers.existing]\ncommand = "echo"\n\n'
+ '[mcp_servers.artemis]\ncommand = "old-command"\n',
+ encoding="utf-8",
+ )
+
+ result = runner.invoke(app, ["mcp", "--install", "codex"])
+ assert result.exit_code == 0
+ result2 = runner.invoke(app, ["mcp", "--install", "codex"])
+ assert result2.exit_code == 0
+
+ import tomllib
+ config_text = config_file.read_text(encoding="utf-8")
+ data = tomllib.loads(config_text)
+ assert data["model"] == "test-model"
+ assert data["mcp_servers"]["existing"]["command"] == "echo"
+ assert data["mcp_servers"]["artemis"]["args"] == ["-m", "mcp_server"]
+ assert data["mcp_servers"]["artemis"]["env"]["PYTHONPATH"] == str(tmp_path)
+ assert config_text.count("# BEGIN ARTEMIS MCP CONFIG") == 1
+
+ agents_file = codex_dir / "AGENTS.md"
+ agents_text = agents_file.read_text(encoding="utf-8")
+ assert "Mobile Testing Mindset" in agents_text
+ assert agents_text.count("") == 1
def test_cli_mcp_install_jsonc_and_backup(tmp_path, monkeypatch):