app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
master
John Lauer
2.0.413: missing verbs + behaviors (assisted_click, import, fg-watch, url lint, notices, chrome-stable, eval frame, mov…
0dd55ee
2mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
//! Real Google Chrome install (Node: chrome.js installChromeStable / chromeStableState). pup drives
//! the machine's INSTALLED Chrome or Edge by default; `pup_use {browser:"chrome", install:true}`
//! fetches and installs actual Google Chrome. Windows: the silent installer, escalating to an
//! elevated (UAC) install; macOS: the .dmg copied to /Applications. The phase drives pup_readiness's
//! chromeStable* fields. NO ab notify re-arm loop here (the Node build's peer-ping UX stays in Node
//! until the exe has the direct-API notify channel); we run one elevated prompt and report the phase.
use crate::{detect_chrome, Host};
use serde_json::{json, Value};
use std::sync::Mutex;
use std::time::{Duration, Instant};
#[derive(Clone)]
pub struct StableState { pub phase: String, pub error: Option<String>, pub error_code: Option<String>, pub in_flight: bool }
static STATE: Mutex<Option<StableState>> = Mutex::new(None);
pub fn snapshot() -> Value {
let g = STATE.lock().unwrap();
let st = g.clone().unwrap_or(StableState { phase: "idle".into(), error: None, error_code: None, in_flight: false });
json!({ "installingChrome": st.in_flight, "chromeStablePhase": st.phase, "chromeStableAwaitingApproval": st.phase == "awaiting_uac",
"chromeStableError": st.error, "chromeStableNotifyChannel": Value::Null, "chromeStableNotifyPeers": Value::Null })
}
fn set(phase: &str, code: Option<&str>, err: Option<&str>, in_flight: bool) {
*STATE.lock().unwrap() = Some(StableState { phase: phase.into(), error_code: code.map(|s| s.into()), error: err.map(|s| s.into()), in_flight });
}
/// Kick off the install in the background (idempotent). Poll `snapshot()` / pup_readiness for phase.
pub fn install_background(host: std::sync::Arc<dyn Host>, elevate: Option<bool>) {
{ let g = STATE.lock().unwrap(); if g.as_ref().map(|s| s.in_flight).unwrap_or(false) { return; } }
set("installing", None, None, true);
std::thread::Builder::new().name("chrome-stable".into()).spawn(move || { let _ = install_blocking(&*host, elevate); }).ok();
}
pub fn install_blocking(host: &dyn Host, elevate: Option<bool>) -> Value {
set("installing", None, None, true);
if let Some(c) = detect_chrome(host) { set("ready", None, None, false); return json!({ "ok": true, "executablePath": c.executable_path.to_string_lossy(), "alreadyInstalled": true }); }
let res = if cfg!(target_os = "macos") { install_mac(host) } else if cfg!(windows) { install_win(host, elevate) } else {
set("failed", Some("chrome_stable_unsupported_os"), Some("Auto-installing Google Chrome is only wired up for Windows and macOS."), false);
json!({ "ok": false, "errorCode": "chrome_stable_unsupported_os" })
};
res
}
fn install_mac(host: &dyn Host) -> Value {
let dmg = host.temp_dir().join(format!("chrome_installer_{}.dmg", std::process::id()));
if let Err(e) = host.download_file("https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg", &dmg) { set("failed", Some("chrome_download_failed"), Some(&e), false); return json!({ "ok": false, "errorCode": "chrome_download_failed", "error": e }); }
// hdiutil attach, ditto Google Chrome.app to /Applications, detach. Node: the darwin branch.
let script = format!("set -e; MP=$(hdiutil attach -nobrowse -readonly '{}' | grep Volumes | sed 's/.*\\/Volumes/\\/Volumes/'); ditto \"$MP/Google Chrome.app\" \"/Applications/Google Chrome.app\" || ditto \"$MP/Google Chrome.app\" \"$HOME/Applications/Google Chrome.app\"; hdiutil detach \"$MP\" -quiet; rm -f '{}'", dmg.display(), dmg.display());
let _ = host.run_shell_script(&script, Duration::from_secs(180));
match detect_chrome(host) { Some(c) => { set("ready", None, None, false); json!({ "ok": true, "executablePath": c.executable_path.to_string_lossy() }) } None => { set("failed", Some("chrome_stable_not_detected"), Some("Copied Google Chrome.app but it did not detect afterward."), false); json!({ "ok": false, "errorCode": "chrome_stable_not_detected" }) } }
}
fn install_win(host: &dyn Host, elevate: Option<bool>) -> Value {
let tmp = host.temp_dir().join(format!("chrome_installer_{}.exe", std::process::id()));
if let Err(e) = host.download_file("https://dl.google.com/chrome/install/standalonesetup64.exe", &tmp) { set("failed", Some("chrome_download_failed"), Some(&e), false); return json!({ "ok": false, "errorCode": "chrome_download_failed", "error": e }); }
let tmp_s = tmp.to_string_lossy().replace('\'', "''");
// silent first (no prompt on a normal PC), then poll ~45s
if elevate != Some(true) {
let _ = host.run_shell_script(&format!("Start-Process -FilePath '{tmp_s}' -ArgumentList '/silent','/install' -WindowStyle Hidden"), Duration::from_secs(15));
let t0 = Instant::now(); while t0.elapsed() < Duration::from_secs(45) { if let Some(c) = detect_chrome(host) { let _ = std::fs::remove_file(&tmp); set("ready", None, None, false); return json!({ "ok": true, "executablePath": c.executable_path.to_string_lossy() }); } std::thread::sleep(Duration::from_millis(1500)); }
}
// escalate: one elevated install with a UAC prompt (Start-Process -Verb RunAs)
if elevate != Some(false) {
set("awaiting_uac", None, None, true);
let _ = host.run_shell_script(&format!("try {{ Start-Process -FilePath '{tmp_s}' -ArgumentList '/silent','/install' -Verb RunAs -WindowStyle Hidden -ErrorAction Stop }} catch {{ exit 3 }}"), Duration::from_secs(20));
let t0 = Instant::now(); while t0.elapsed() < Duration::from_secs(150) { if let Some(c) = detect_chrome(host) { let _ = std::fs::remove_file(&tmp); set("ready", None, None, false); return json!({ "ok": true, "executablePath": c.executable_path.to_string_lossy() }); } std::thread::sleep(Duration::from_millis(2500)); }
}
let _ = std::fs::remove_file(&tmp);
let (code, msg) = if elevate == Some(false) { ("chrome_stable_not_detected", "Ran the Chrome installer but chrome.exe did not appear (elevation was declined via elevate:false). pup still works with Edge in the meantime.") } else { ("chrome_stable_needs_approval", "Chrome needs a Windows approval (UAC) that was not accepted in time. Re-run pup_use {browser:\"chrome\", install:true} and click YES on the prompt; pup drives Edge until then.") };
set("failed", Some(code), Some(msg), false);
json!({ "ok": false, "errorCode": code, "error": msg })
}