diff --git a/crates/gyandoot-agent/src/browser_manager.rs b/crates/gyandoot-agent/src/browser_manager.rs index 6e3ef30..97e5257 100644 --- a/crates/gyandoot-agent/src/browser_manager.rs +++ b/crates/gyandoot-agent/src/browser_manager.rs @@ -11,12 +11,19 @@ //! 3. Waits for the Unix socket to appear (up to 30 seconds) //! 4. Returns a ready-to-use `BrowserClient` //! -//! # Cross-platform npx resolution +//! Cross-platform npx resolution //! //! The daemon is started via Node.js's `npx`. Resolution order: //! 1. `config.browser.node_path` — explicit path in config (e.g. //! `/home/user/.nvm/versions/node/v22/bin/npx`) //! 2. `"npx"` on Linux/macOS, `"npx.cmd"` on Windows — PATH lookup +//! +//! WSL Detection +//! +//! Windows Subsystem for Linux is detected by reading `/proc/version` for +//! the presence of "microsoft" or "wsl" (case-insensitive). WSL-specific +//! error messages guide users to ensure Node.js is installed via nvm or +//! fnm inside WSL (not via the Windows-side Node). use std::time::Duration; @@ -61,6 +68,28 @@ fn resolve_npx(node_path: Option<&str>) -> &str { } } +/// Detect if we're running inside Windows Subsystem for Linux (WSL). +/// +/// Checks `/proc/version` for the presence of "microsoft" or "wsl" +/// (case-insensitive). Returns `true` if WSL is detected. +fn is_wsl() -> bool { + std::fs::read_to_string("/proc/version") + .map(|content| { + let lower = content.to_lowercase(); + lower.contains("microsoft") || lower.contains("wsl") + }) + .unwrap_or(false) +} + +/// Get a platform-aware example npx path for error messages. +fn example_npx_path() -> &'static str { + if cfg!(target_os = "macos") { + "\"/Users/yourname/.nvm/versions/node/v22/bin/npx\"" + } else { + "\"/home/user/.nvm/versions/node/v22/bin/npx\"" + } +} + /// Initialize the camoufox-browser daemon and return a ready client. /// /// `node_path` — optional explicit path to `npx`/`npx.cmd` (from @@ -93,7 +122,7 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult { } Ok(false) => { let msg = format!( - "Daemon did not start within {}s — socket not found at {}", + "Daemon did not start within {}s \u{2014} socket not found at {}", DAEMON_STARTUP_TIMEOUT_SECS, socket_path.display() ); @@ -109,18 +138,33 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult { } Err(e) => { let npx_name = resolve_npx(node_path); - let msg = format!( + let mut msg = format!( "Could not start camoufox daemon: {}\n\n\ Make sure Node.js v20+ and the package are installed:\n \ npm install -g camoufox-browser\n \ {} camoufox-browser@latest install\n\n\ If Node is installed via nvm/fnm/asdf, set browser.node_path\n\ - in your config, e.g.:\n \ + in your config:\n \ [browser]\n \ - node_path = \"/home/user/.nvm/versions/node/v22/bin/npx\"\n\n\ + node_path = {}\n\n\ Then try again.", - e, npx_name + e, npx_name, example_npx_path() ); + + // Add WSL-specific hints + if is_wsl() { + msg.push_str( + "\n\n\u{26a0}\u{fe0f} WSL detected: Make sure Node.js is installed INSIDE WSL\n\ + (e.g., via nvm or fnm in your WSL terminal). The Windows-side\n\ + Node.js is not accessible from WSL by default.\n\ + Test with: which npx\n\ + If 'which npx' returns empty, run:\n \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n \ + nvm install 22\n\ + Then run 'gyandoot run' again.", + ); + } + warn!("{}", msg); BrowserInitResult::Unavailable(msg) } @@ -148,11 +192,27 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result< .await .map_err(|e| { let hint = if node_path.is_none() { - "\n → Set [browser].node_path in your config, e.g.:\n [browser]\n node_path = \"/Users/yourname/.nvm/versions/node/v22/bin/npx\"".to_string() + format!( + "\n \u{2192} Set [browser].node_path in your config:\n [browser]\n node_path = {}", + example_npx_path() + ) } else { - format!("\n → npx at '{}' not found or not executable", npx) + format!("\n \u{2192} npx at '{}' not found or not executable", npx) }; - format!("{} failed: {}. Is Node.js installed?{}", npx, e, hint) + + let wsl_hint = if is_wsl() { + "\n\n\u{26a0}\u{fe0f} WSL detected: npx was not found. Install Node.js inside WSL:\n \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n \ + nvm install 22" + .to_string() + } else { + String::new() + }; + + format!( + "{} failed: {}. Is Node.js installed?{}{}", + npx, e, hint, wsl_hint + ) })?; if !output.status.success() { @@ -169,17 +229,27 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result< )); let hint = if node_path.is_none() { - "\n → Try setting [browser].node_path in your config:\n [browser]\n node_path = \"/Users/yourname/.nvm/versions/node/v22/bin/npx\"".to_string() + format!( + "\n \u{2192} Try setting [browser].node_path in your config:\n [browser]\n node_path = {}", + example_npx_path() + ) + } else { + String::new() + }; + + let wsl_hint = if is_wsl() { + "".to_string() } else { String::new() }; return Err(format!( - "camoufox-browser failed (exit: {}):\n {}\n → Full log saved to {}{}", + "camoufox-browser failed (exit: {}):\n {}\n \u{2192} Full log saved to {}{}{}", output.status.code().unwrap_or(-1), details, log_file.display(), hint, + wsl_hint, )); }