diff --git a/crates/gyandoot-agent/src/browser_manager.rs b/crates/gyandoot-agent/src/browser_manager.rs index 8342de0..bb193b7 100644 --- a/crates/gyandoot-agent/src/browser_manager.rs +++ b/crates/gyandoot-agent/src/browser_manager.rs @@ -326,17 +326,46 @@ async fn run_fetch(npx: &str, js_version: &str) -> Result { .to_string()) } -/// Check if an npm package is already installed globally. -async fn is_npm_package_installed(npx: &str, package: &str, version: &str) -> bool { - Command::new(npx) - .args(&["--yes", &format!("{package}@{version}"), "--version"]) +/// Check if an npm package is already installed globally by checking for its daemon.js on disk. +/// We do NOT run `npx --version` because that actually loads the package code, +/// which fails if we've removed the bundled camoufox-js dependency. +async fn is_npm_package_installed(_npx: &str, package: &str, _version: &str) -> bool { + // Try `npm root -g` to find global node_modules + let root_output = Command::new("npm") + .args(&["root", "-g"]) .output() - .await - .map(|o| o.status.success()) - .unwrap_or(false) + .await; + let global_root = match root_output { + Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).trim().to_string(), + _ => return false, + }; + let daemon_path = std::path::Path::new(&global_root) + .join(package) + .join("dist") + .join("daemon.js"); + if daemon_path.exists() { + return true; + } + // Fallback: check common locations + let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/tmp")); + let fallbacks = vec![ + home.join(".nvm").join("versions").join("node").join("v22").join("lib").join("node_modules") + .join(package).join("dist").join("daemon.js"), + home.join(".nvm").join("versions").join("node").join("v24").join("lib").join("node_modules") + .join(package).join("dist").join("daemon.js"), + std::path::PathBuf::from("/usr/local/lib/node_modules").join(package).join("dist").join("daemon.js"), + std::path::PathBuf::from("/opt/homebrew/lib/node_modules").join(package).join("dist").join("daemon.js"), + ]; + for fb in &fallbacks { + if fb.exists() { + return true; + } + } + false } -/// Install an npm package globally. +/// Install an npm package globally, then remove the bundled camoufox-js +/// so the daemon uses the globally installed version instead. async fn install_npm_package(_npx: &str, package: &str, version: &str) -> Result { let output = Command::new("npm") .args(&["install", "-g", &format!("{package}@{version}")]) @@ -349,6 +378,25 @@ async fn install_npm_package(_npx: &str, package: &str, version: &str) -> Result return Err(stderr.lines().last().unwrap_or("(unknown)").to_string()); } + // After install, remove the bundled camoufox-js so Node resolves to the global version + let root_output = Command::new("npm") + .args(&["root", "-g"]) + .output() + .await; + if let Ok(o) = root_output { + if o.status.success() { + let global_root = String::from_utf8_lossy(&o.stdout).trim().to_string(); + let bundled = std::path::Path::new(&global_root) + .join(package) + .join("node_modules") + .join("camoufox-js"); + if bundled.exists() { + let _ = std::fs::remove_dir_all(&bundled); + info!("Removed bundled camoufox-js from {} (daemon will use global version)", package); + } + } + } + Ok(format!("{package}@{version} installed")) }