From dfd15073ab41e5fca725931c303c27e64378cebe Mon Sep 17 00:00:00 2001 From: xuan20021031 <416100240224@email.ncu.edu.cn> Date: Fri, 3 Jul 2026 14:44:16 +0800 Subject: [PATCH] fix(windows): use correct yt-dlp config path and resolve .CMD shims in subprocess Two Windows-install bugs in cli.py: 1. yt-dlp JS runtime config was written to ~/.config/yt-dlp/config (Linux path). On Windows yt-dlp reads %APPDATA%/yt-dlp/config, so install reported 'configured' while doctor (which already uses the cross-platform get_ytdlp_config_path helper) reported it unconfigured. Reuse the existing helper so install and doctor agree. 2. _install_mcporter called subprocess.run(['npm', ...]) / ['mcporter'] with bare command names. On Windows these are .CMD shims and CreateProcess does not append PATHEXT, so subprocess raised FileNotFoundError (WinError 2). Resolve via shutil.which first (mirrors probe_command and the existing undici install path). Verified on Windows 11: install now writes %APPDATA%/yt-dlp/config, doctor reports YouTube as configured, and npm/mcporter subprocess calls succeed instead of raising WinError 2. --- agent_reach/cli.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/agent_reach/cli.py b/agent_reach/cli.py index 3cd45f61..1fe4b1f5 100644 --- a/agent_reach/cli.py +++ b/agent_reach/cli.py @@ -634,8 +634,15 @@ def _install_system_deps(): # ── yt-dlp JS runtime config (YouTube requires external JS runtime) ── if shutil.which("node"): - ytdlp_config_dir = os.path.expanduser("~/.config/yt-dlp") - ytdlp_config = os.path.join(ytdlp_config_dir, "config") + # Use the cross-platform helper so the config lands where yt-dlp + # actually reads it on every OS. The previous hardcoded + # ~/.config/yt-dlp path is Linux-only; on Windows yt-dlp reads + # %APPDATA%/yt-dlp/config, so install wrote to a location yt-dlp + # never consults and doctor (which uses this helper) reported the + # runtime as unconfigured. See utils/paths.get_ytdlp_config_path. + from agent_reach.utils.paths import get_ytdlp_config_path + ytdlp_config = str(get_ytdlp_config_path()) + ytdlp_config_dir = os.path.dirname(ytdlp_config) needs_config = True if os.path.exists(ytdlp_config): with open(ytdlp_config, "r") as f: @@ -918,20 +925,33 @@ def _install_mcporter(): print("Setting up mcporter (search backend)...") - if shutil.which("mcporter"): + # Resolve command short names to their full path before subprocess.run. + # On Windows, npm/mcporter/npx are .CMD/.BAT shims; CreateProcess does + # not append PATHEXT, so subprocess.run(["npm", ...]) raises + # FileNotFoundError (WinError 2) even though shutil.which("npm") finds + # it. which() returns the path with the real extension, so executing + # that path directly works on every platform. Mirrors probe_command. + def _which(cmd): + return shutil.which(cmd) + + npm_path = _which("npm") or _which("npx") + mcporter_path = _which("mcporter") + + if mcporter_path: print(" ✅ mcporter already installed") else: # Check for npm/npx - if not shutil.which("npm") and not shutil.which("npx"): + if not npm_path: print(" [!] mcporter requires Node.js. Install Node.js first:") print(" https://nodejs.org/ or: curl -fsSL https://fnm.vercel.app/install | bash") return try: subprocess.run( - ["npm", "install", "-g", "mcporter"], + [npm_path, "install", "-g", "mcporter"], capture_output=True, encoding="utf-8", errors="replace", timeout=120, ) - if shutil.which("mcporter"): + mcporter_path = _which("mcporter") + if mcporter_path: print(" ✅ mcporter installed") else: print(" [X] mcporter install failed. Retry: npm install -g mcporter (check network/timeout), or try: npx mcporter@latest list") @@ -943,11 +963,11 @@ def _install_mcporter(): # Configure Exa MCP (free, no key needed) try: r = subprocess.run( - ["mcporter", "config", "list"], capture_output=True, encoding="utf-8", errors="replace", timeout=5 + [mcporter_path, "config", "list"], capture_output=True, encoding="utf-8", errors="replace", timeout=5 ) if "exa" not in r.stdout: subprocess.run( - ["mcporter", "config", "add", "exa", "https://mcp.exa.ai/mcp"], + [mcporter_path, "config", "add", "exa", "https://mcp.exa.ai/mcp"], capture_output=True, encoding="utf-8", errors="replace", timeout=10, ) print(" ✅ Exa search configured (free, no API key needed)")