diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 38053a7..7f38b0d 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -36,7 +36,7 @@ jobs: - { name: macOS ARM64, os: macos-latest, target: aarch64-apple-darwin, asset: agent-browser-darwin-arm64, use_zigbuild: false, ext: '' } steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.inputs.tag || github.ref }} @@ -102,7 +102,7 @@ jobs: ) - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.asset }} path: dist/${{ matrix.asset }}.tar.gz* @@ -117,7 +117,7 @@ jobs: contents: write steps: - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: dist merge-multiple: true @@ -126,7 +126,7 @@ jobs: run: ls -la dist - name: Attach to release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ github.event.inputs.tag || github.ref_name }} files: | diff --git a/cli/src/upgrade.rs b/cli/src/upgrade.rs index 14642ac..3b8ed85 100644 --- a/cli/src/upgrade.rs +++ b/cli/src/upgrade.rs @@ -1,284 +1,70 @@ use crate::color; -use std::path::Path; -use std::process::{exit, Command, Stdio}; +use std::process::{exit, Command}; const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); -const NPM_REGISTRY_URL: &str = "https://registry.npmjs.org/agent-browser/latest"; -enum InstallMethod { - Npm, - Pnpm, - Yarn, - Bun, - Homebrew, - Cargo, - Unknown, -} - -async fn fetch_latest_version() -> Result { - let resp = reqwest::get(NPM_REGISTRY_URL) - .await - .map_err(|e| format!("Failed to fetch version info: {}", e))?; - - let body: serde_json::Value = resp - .json() - .await - .map_err(|e| format!("Failed to parse version info: {}", e))?; - - body.get("version") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()) - .ok_or_else(|| "No version field in registry response".to_string()) -} - -/// Parse the `.install-method` marker written by postinstall.js. -fn read_install_method_marker(exe_dir: &Path) -> Option { - let contents = std::fs::read_to_string(exe_dir.join(".install-method")).ok()?; - match contents.trim() { - "npm" => Some(InstallMethod::Npm), - "pnpm" => Some(InstallMethod::Pnpm), - "yarn" => Some(InstallMethod::Yarn), - "bun" => Some(InstallMethod::Bun), - _ => None, - } -} - -fn detect_install_method() -> InstallMethod { - if let Ok(exe) = std::env::current_exe() { - // Resolve symlinks to find the real binary location - let real_path = exe.canonicalize().unwrap_or(exe); - - // Preferred: read the marker file written at install time - if let Some(dir) = real_path.parent() { - if let Some(method) = read_install_method_marker(dir) { - return method; - } - } - - // Fallback: infer from executable path - let path_str = real_path.to_string_lossy(); - - if path_str.contains("/.cargo/bin/") || path_str.contains("\\.cargo\\bin\\") { - return InstallMethod::Cargo; - } - - if path_str.contains("/Cellar/agent-browser/") - || path_str.contains("/homebrew/") - || path_str.contains("/linuxbrew/") - { - return InstallMethod::Homebrew; - } - - if path_str.contains("/pnpm/") || path_str.contains("/pnpm-global/") { - return InstallMethod::Pnpm; - } - - if path_str.contains("/.yarn/") || path_str.contains("/yarn/global/") { - return InstallMethod::Yarn; - } - - if path_str.contains("/.bun/") { - return InstallMethod::Bun; - } - - if path_str.contains("node_modules/agent-browser") - || path_str.contains("node_modules\\agent-browser") - { - return InstallMethod::Npm; - } - } - - // Last resort: probe package managers via subprocess - - #[cfg(any(target_os = "macos", target_os = "linux"))] - { - if command_succeeds("brew", &["list", "agent-browser"]) { - return InstallMethod::Homebrew; - } - } - - if command_output_contains( - "pnpm", - &["list", "-g", "agent-browser", "--depth=0"], - "agent-browser", - ) { - return InstallMethod::Pnpm; - } - - if command_output_contains("yarn", &["global", "list", "--depth=0"], "agent-browser") { - return InstallMethod::Yarn; - } - - if command_output_contains("bun", &["pm", "ls", "-g"], "agent-browser") { - return InstallMethod::Bun; - } - - if command_succeeds("npm", &["list", "-g", "agent-browser", "--depth=0"]) { - return InstallMethod::Npm; - } - - InstallMethod::Unknown -} - -fn command_succeeds(cmd: &str, args: &[&str]) -> bool { - Command::new(cmd) - .args(args) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map(|s| s.success()) - .unwrap_or(false) -} - -fn command_output_contains(cmd: &str, args: &[&str], needle: &str) -> bool { - Command::new(cmd) - .args(args) - .stderr(Stdio::null()) - .output() - .map(|o| o.status.success() && String::from_utf8_lossy(&o.stdout).contains(needle)) - .unwrap_or(false) -} - -fn run_upgrade_command(method: &InstallMethod) -> bool { - let (cmd, args, display): (&str, &[&str], &str) = match method { - InstallMethod::Npm => ( - "npm", - &["install", "-g", "agent-browser@latest"], - "npm install -g agent-browser@latest", - ), - InstallMethod::Pnpm => ( - "pnpm", - &["add", "-g", "agent-browser@latest"], - "pnpm add -g agent-browser@latest", - ), - // NOTE: `yarn global` is Yarn Classic (v1) only; Yarn Berry (v2+) removed it. - // Users on Yarn v2+ won't reach this path — detection falls through to Unknown. - InstallMethod::Yarn => ( - "yarn", - &["global", "add", "agent-browser@latest"], - "yarn global add agent-browser@latest", - ), - InstallMethod::Bun => ( - "bun", - &["install", "-g", "agent-browser@latest"], - "bun install -g agent-browser@latest", - ), - InstallMethod::Homebrew => ( - "brew", - &["upgrade", "agent-browser"], - "brew upgrade agent-browser", - ), - InstallMethod::Cargo => ( - "cargo", - &["install", "agent-browser", "--force"], - "cargo install agent-browser --force", - ), - InstallMethod::Unknown => return false, - }; - - println!("Running: {}", display); - Command::new(cmd) - .args(args) - .status() - .map(|s| s.success()) - .unwrap_or(false) -} +/// Canonical installer for the stealth fork. `upgrade` just re-runs it, so the +/// upgrade path and the install path are identical (GitHub Release, no npm). +const INSTALL_URL: &str = + "https://raw.githubusercontent.com/leeguooooo/agent-browser-stealth/main/install.sh"; +/// Upgrade to the latest GitHub Release. +/// +/// The stealth fork ships as a prebuilt binary attached to a GitHub Release — +/// NOT via the npm registry. Earlier this command (inherited from upstream) +/// ran `npm/pnpm install -g agent-browser@latest`, which installed the +/// UNRELATED upstream `agent-browser` package and clobbered the user's setup. +/// Now `upgrade` simply re-runs install.sh into the same directory as the +/// current binary, so it always tracks the freshest GitHub Release. pub fn run_upgrade() { - let current = CURRENT_VERSION; + println!( + "{}", + color::cyan(&format!( + "Upgrading agent-browser-stealth (currently v{}) from the latest GitHub Release...", + CURRENT_VERSION + )) + ); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap_or_else(|e| { - eprintln!( - "{} Failed to create runtime: {}", - color::error_indicator(), - e - ); - exit(1); - }); - - let latest = match rt.block_on(fetch_latest_version()) { - Ok(v) => v, - Err(e) => { - eprintln!( - "{} Could not check latest version: {}", - color::warning_indicator(), - e - ); - String::new() - } - }; - - if !latest.is_empty() && current == latest.as_str() { - println!( - "{} agent-browser is already at the latest version (v{})", - color::success_indicator(), - current - ); - return; - } - - let method = detect_install_method(); - - let method_name = match &method { - InstallMethod::Npm => "npm", - InstallMethod::Pnpm => "pnpm", - InstallMethod::Yarn => "yarn", - InstallMethod::Bun => "bun", - InstallMethod::Homebrew => "Homebrew", - InstallMethod::Cargo => "Cargo", - InstallMethod::Unknown => "", - }; - - if matches!(method, InstallMethod::Unknown) { + #[cfg(windows)] + { eprintln!( - "{} Could not detect installation method.", - color::error_indicator() + "{} Automatic upgrade isn't supported on Windows.", + color::warning_indicator() ); - eprintln!(" To update manually, run one of:"); - eprintln!(" npm install -g agent-browser@latest # npm"); - eprintln!(" pnpm add -g agent-browser@latest # pnpm"); - eprintln!(" yarn global add agent-browser@latest # yarn"); - eprintln!(" bun install -g agent-browser@latest # bun"); - eprintln!(" brew upgrade agent-browser # Homebrew"); - eprintln!(" cargo install agent-browser --force # Cargo"); + eprintln!(" Download the latest agent-browser-win32-x64.tar.gz from:"); + eprintln!(" https://github.com/leeguooooo/agent-browser-stealth/releases/latest"); + eprintln!(" and replace agent-browser.exe on your PATH."); exit(1); } - println!("Detected installation via {}.", method_name); + #[cfg(not(windows))] + { + // Install into the SAME directory as the running binary (in-place + // upgrade), so we don't create a second copy elsewhere on PATH. + let bin_dir = std::env::current_exe() + .ok() + .and_then(|p| p.canonicalize().ok()) + .and_then(|p| p.parent().map(|d| d.to_path_buf())); - if !latest.is_empty() { - println!( - "{}", - color::cyan(&format!( - "Upgrading agent-browser... v{} → v{}", - current, latest - )) - ); - } else { - println!( - "{}", - color::cyan(&format!("Upgrading agent-browser (v{})...", current)) - ); - } + let install_cmd = format!("curl -fsSL {} | sh", INSTALL_URL); + println!("Running: {}", install_cmd); - let success = run_upgrade_command(&method); + let mut cmd = Command::new("sh"); + cmd.arg("-c").arg(&install_cmd); + if let Some(ref dir) = bin_dir { + cmd.env("AGENT_BROWSER_BIN_DIR", dir); + } - if success { - if !latest.is_empty() { + let ok = cmd.status().map(|s| s.success()).unwrap_or(false); + if ok { println!( - "{} Done! v{} → v{}", - color::success_indicator(), - current, - latest + "{} Upgrade complete — run `agent-browser-stealth --version` to confirm.", + color::success_indicator() ); } else { - println!("{} Done!", color::success_indicator()); + eprintln!("{} Upgrade failed. Install manually:", color::error_indicator()); + eprintln!(" curl -fsSL {} | sh", INSTALL_URL); + exit(1); } - } else { - eprintln!("{} Upgrade failed.", color::error_indicator()); - exit(1); } }