From 8cfba1752d794e67856dfca67cd424a7a776b0d3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 17 Mar 2026 23:29:07 -0500 Subject: [PATCH] feat: add built-in `upgrade` command for self-update (#898) Adds a new `agent-browser upgrade` command that automatically detects the installation method (npm, Homebrew, or Cargo) and runs the appropriate update command. **Changes:** - Added new `upgrade.rs` module with upgrade logic - Updated `main.rs` to handle the `upgrade` command - Added upgrade help text in `output.rs` - Updated README.md and documentation with upgrade instructions - Updated SKILL.md to mention the upgrade command **Implementation details:** - Fetches latest version from npm registry to show version diff - Auto-detects installation method by checking Homebrew, Cargo paths, and npm global packages - Provides fallback instructions if installation method cannot be determined - Uses existing color module for consistent styled output - Gracefully handles network failures and continues with upgrade Fixes #895 --- README.md | 11 ++ cli/src/main.rs | 8 ++ cli/src/output.rs | 17 +++ cli/src/upgrade.rs | 186 +++++++++++++++++++++++++++++ docs/src/app/installation/page.mdx | 10 ++ skills/agent-browser/SKILL.md | 2 +- 6 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 cli/src/upgrade.rs diff --git a/README.md b/README.md index b419bb8..1b614c4 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,16 @@ On Linux, install system dependencies: agent-browser install --with-deps ``` +### Updating + +Upgrade to the latest version: + +```bash +agent-browser upgrade +``` + +Detects your installation method (npm, Homebrew, or Cargo) and runs the appropriate update command automatically. + ### Requirements - **Chrome** - Run `agent-browser install` to download Chrome from [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing/) (Google's official automation channel). No Playwright or Node.js required for the daemon. @@ -339,6 +349,7 @@ agent-browser reload # Reload page ```bash agent-browser install # Download Chrome from Chrome for Testing (Google's official automation channel) agent-browser install --with-deps # Also install system deps (Linux) +agent-browser upgrade # Upgrade agent-browser to the latest version ``` ## Authentication diff --git a/cli/src/main.rs b/cli/src/main.rs index 53d63b9..448add0 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -7,6 +7,7 @@ mod native; mod output; #[cfg(test)] mod test_utils; +mod upgrade; mod validation; use serde_json::json; @@ -26,6 +27,7 @@ use install::run_install; use output::{ print_command_help, print_help, print_response_with_opts, print_version, OutputOptions, }; +use upgrade::run_upgrade; fn serialize_json_value(value: &serde_json::Value) -> String { serde_json::to_string(value).unwrap_or_else(|_| { @@ -226,6 +228,12 @@ fn main() { return; } + // Handle upgrade separately + if clean.first().map(|s| s.as_str()) == Some("upgrade") { + run_upgrade(); + return; + } + // Handle session separately (doesn't need daemon) if clean.first().map(|s| s.as_str()) == Some("session") { run_session(&clean, &flags.session, flags.json); diff --git a/cli/src/output.rs b/cli/src/output.rs index b0f948a..336f283 100644 --- a/cli/src/output.rs +++ b/cli/src/output.rs @@ -2266,6 +2266,22 @@ Examples: "## } + // === Upgrade === + "upgrade" => { + r##" +agent-browser upgrade - Upgrade to the latest version + +Usage: agent-browser upgrade + +Detects the current installation method (npm, Homebrew, or Cargo) and runs +the appropriate update command. Displays the version change on success, or +informs you if you are already on the latest version. + +Examples: + agent-browser upgrade +"## + } + // === Connect === "connect" => { r##" @@ -2572,6 +2588,7 @@ Sessions: Setup: install Install browser binaries install --with-deps Also install system dependencies (Linux) + upgrade Upgrade to the latest version Snapshot Options: -i, --interactive Only interactive elements diff --git a/cli/src/upgrade.rs b/cli/src/upgrade.rs new file mode 100644 index 0000000..8fbedd5 --- /dev/null +++ b/cli/src/upgrade.rs @@ -0,0 +1,186 @@ +use crate::color; +use std::process::{exit, Command, Stdio}; + +const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); +const NPM_REGISTRY_URL: &str = "https://registry.npmjs.org/agent-browser/latest"; + +enum InstallMethod { + Npm, + 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()) +} + +fn detect_install_method() -> InstallMethod { + // Check Homebrew (available on macOS and Linux) + #[cfg(any(target_os = "macos", target_os = "linux"))] + { + let brew_check = Command::new("brew") + .args(["list", "agent-browser"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + if brew_check.map(|s| s.success()).unwrap_or(false) { + return InstallMethod::Homebrew; + } + } + + // Check Cargo installation by executable path + if let Ok(exe) = std::env::current_exe() { + let path_str = exe.to_string_lossy(); + if path_str.contains("/.cargo/bin/") || path_str.contains("\\.cargo\\bin\\") { + return InstallMethod::Cargo; + } + } + + // Check npm global installation + let npm_check = Command::new("npm") + .args(["list", "-g", "agent-browser", "--depth=0"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + if npm_check.map(|s| s.success()).unwrap_or(false) { + return InstallMethod::Npm; + } + + InstallMethod::Unknown +} + +fn run_upgrade_command(method: &InstallMethod) -> bool { + match method { + InstallMethod::Npm => { + println!("Running: npm install -g agent-browser@latest"); + Command::new("npm") + .args(["install", "-g", "agent-browser@latest"]) + .status() + .map(|s| s.success()) + .unwrap_or(false) + } + InstallMethod::Homebrew => { + println!("Running: brew upgrade agent-browser"); + Command::new("brew") + .args(["upgrade", "agent-browser"]) + .status() + .map(|s| s.success()) + .unwrap_or(false) + } + InstallMethod::Cargo => { + println!("Running: cargo install agent-browser --force"); + Command::new("cargo") + .args(["install", "agent-browser", "--force"]) + .status() + .map(|s| s.success()) + .unwrap_or(false) + } + InstallMethod::Unknown => false, + } +} + +pub fn run_upgrade() { + let current = 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::Homebrew => "Homebrew", + InstallMethod::Cargo => "Cargo", + InstallMethod::Unknown => "", + }; + + if matches!(method, InstallMethod::Unknown) { + eprintln!( + "{} Could not detect installation method.", + color::error_indicator() + ); + eprintln!(" To update manually, run one of:"); + eprintln!(" npm install -g agent-browser@latest # npm"); + eprintln!(" brew upgrade agent-browser # Homebrew"); + eprintln!(" cargo install agent-browser --force # Cargo"); + exit(1); + } + + println!("Detected installation via {}.", method_name); + + 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 success = run_upgrade_command(&method); + + if success { + if !latest.is_empty() { + println!( + "{} Done! v{} → v{}", + color::success_indicator(), + current, + latest + ); + } else { + println!("{} Done!", color::success_indicator()); + } + } else { + eprintln!("{} Upgrade failed.", color::error_indicator()); + exit(1); + } +} diff --git a/docs/src/app/installation/page.mdx b/docs/src/app/installation/page.mdx index 6990ec9..92164b1 100644 --- a/docs/src/app/installation/page.mdx +++ b/docs/src/app/installation/page.mdx @@ -69,6 +69,16 @@ On Linux, install system dependencies: agent-browser install --with-deps ``` +## Updating + +Upgrade to the latest version: + +```bash +agent-browser upgrade +``` + +Detects your installation method (npm, Homebrew, or Cargo) and runs the appropriate update command automatically. Displays the version change on success, or informs you if you are already on the latest version. + ## Custom browser Use a custom browser executable instead of bundled Chromium: diff --git a/skills/agent-browser/SKILL.md b/skills/agent-browser/SKILL.md index 0a11d8a..73bd784 100644 --- a/skills/agent-browser/SKILL.md +++ b/skills/agent-browser/SKILL.md @@ -6,7 +6,7 @@ allowed-tools: Bash(npx agent-browser:*), Bash(agent-browser:*) # Browser Automation with agent-browser -The CLI uses Chrome/Chromium via CDP directly. Install via `npm i -g agent-browser`, `brew install agent-browser`, or `cargo install agent-browser`. Run `agent-browser install` to download Chrome. +The CLI uses Chrome/Chromium via CDP directly. Install via `npm i -g agent-browser`, `brew install agent-browser`, or `cargo install agent-browser`. Run `agent-browser install` to download Chrome. Run `agent-browser upgrade` to update to the latest version. ## Core Workflow