diff --git a/src/context_engine/cli.py b/src/context_engine/cli.py index 1db7cf7..1cb21c5 100644 --- a/src/context_engine/cli.py +++ b/src/context_engine/cli.py @@ -922,6 +922,19 @@ def init(ctx: click.Context, agent: str) -> None: section = _editor_section(editor, project_dir) click.echo(_dim(f" ~/{editor['config_path']} → [{section}]")) + # In auto mode, show which agents weren't detected so the user knows + # they can use --agent to force configuration. + if agent == "auto": + # Only mention agents that can be forced via --agent + configurable_keys = set() + for _agent_name, editor_keys in _INIT_AGENT_TO_EDITORS.items(): + configurable_keys.update(editor_keys) + skipped = configurable_keys - editor_targets + if skipped: + agent_names = [a for a, keys in _INIT_AGENT_TO_EDITORS.items() if keys & skipped] + names = ", ".join(sorted(agent_names)) + click.echo(_dim(f" Not detected: {names}. Use --agent to configure manually.")) + # Write instruction files for the selected editors. In `auto` mode, also # pick up instruction files whose marker exists even if the editor itself # wasn't detected (e.g. an `AGENTS.md` checked in without a `~/.codex/`). diff --git a/src/context_engine/editors.py b/src/context_engine/editors.py index 8b056b8..195fe58 100644 --- a/src/context_engine/editors.py +++ b/src/context_engine/editors.py @@ -217,7 +217,12 @@ def _toml_quote(s: str) -> str: def detect_editors(project_dir: Path) -> list[str]: """Return list of editor keys detected for this project. Markers are - looked up under each editor's scope root (project dir or home dir).""" + looked up under each editor's scope root (project dir or home dir). + + For Codex, also checks for the VS Code extension directory + (``~/.vscode/extensions/openai.*``) since the extension doesn't + create ``~/.codex`` until the CLI is run separately. + """ found = [] for key, editor in EDITORS.items(): root = _scope_root(editor, project_dir) @@ -225,9 +230,23 @@ def detect_editors(project_dir: Path) -> list[str]: if (root / marker).exists(): found.append(key) break + else: + # Secondary detection for Codex: VS Code extension installed + if key == "codex" and _has_vscode_openai_extension(): + found.append(key) return found +def _has_vscode_openai_extension() -> bool: + """Check if any OpenAI VS Code extension is installed (as a proxy for Codex) + by looking for extension directories matching ``openai.*`` under + ``~/.vscode/extensions``. No subprocess needed, works cross-platform.""" + ext_dir = Path.home() / ".vscode" / "extensions" + if not ext_dir.is_dir(): + return False + return any(ext_dir.glob("openai.*")) + + def _codex_toml_block(command: str, project_dir: str, *, section: str) -> str: """Generate one TOML mcp_servers block. Section is the full dotted key rendered from the editor's section_template (e.g. `mcp_servers.cce-myapp-a3f2`). diff --git a/tests/test_editors_codex.py b/tests/test_editors_codex.py index d38785f..adbcbb5 100644 --- a/tests/test_editors_codex.py +++ b/tests/test_editors_codex.py @@ -130,6 +130,22 @@ def test_no_codex_detection_when_home_codex_absent(fake_home, project_dir): assert "codex" not in detect_editors(project_dir) +def test_detect_codex_via_vscode_extension(fake_home, project_dir): + """Codex VS Code extension installed but ~/.codex doesn't exist yet. + Detection should still fire via the extension directory.""" + ext_dir = fake_home / ".vscode" / "extensions" / "openai.openai-chatgpt-adhoc-1.0.0" + ext_dir.mkdir(parents=True) + # ~/.codex does NOT exist + assert not (fake_home / ".codex").exists() + detected = detect_editors(project_dir) + assert "codex" in detected + + +def test_no_codex_detection_without_any_signal(fake_home, project_dir): + """Neither ~/.codex nor VS Code extension present — no detection.""" + assert "codex" not in detect_editors(project_dir) + + # ── Configure: writes to ~/.codex/config.toml ──────────────────────────────── def test_configure_writes_to_user_global_codex_config(fake_home, project_dir):