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.
← Commit history
v2.0.712 (insiders): pup says when a page load lands on a bot wall: Cloudflare (Just a moment / Verify you are human), PerimeterX (press and hold), Akamai (Access Denied), DataDome and generic bot checks come back as botWall with what to do instead (stop loading that site, use adom-parts-search for parts data, or ask the user for one click), not as a normal load. Loading pages from DigiKey, Mouser, Arrow, TI, Analog Devices, ST, Octopart or Newark within 15 s of the last one adds a pacing warning (John's ECAS window, 2026-10-11). Gates on arav-rog: smoke all pass, foreground pass, parity 55/56, jump list 12/15.
4 files changed
+65−14
SKILL.md+14@@ -459,6 +459,20 @@ Do not reopen with `lane:"cdp"` for input or eval; the `pup-lanes` skill is the including the user's real Chrome driven by the real extension (analog.com does exactly this on a cold profile). That is a human-visit problem, not a driver problem, surface it and let the human load it once. +### When a page comes back as `botWall` (2.0.712)++Every page load (open, navigate, new tab, reload, back, forward) is checked for a bot wall. If the site answered+with Cloudflare ("Just a moment", "Verify you are human"), PerimeterX ("press and hold"), Akamai ("Access Denied"),+DataDome or a generic bot check, the reply carries `botWall: {vendor, host, url}` and a `_hint`. Then:++- **Stop loading pages from that host.** Every retry raises the site's bot score and makes the wall harder.+- **Parts data (stock, price, datasheet): use `adom-parts-search`,** not product pages.+- **Need the page itself:** ask the user for ONE click on the check in that window (`pup_alert_window`, or+ `pup_raise_os_window` with a reason), or open it in their own browser with `nbrowser_*`. Never solve it yourself.++A `_paceWarning` means you loaded DigiKey, Mouser, Arrow, TI, Analog Devices, ST, Octopart or Newark again+within 15 s. Slow down before the wall goes up: one page at a time, 15 s or more apart.+ ## When pup: vs the Adom extension (`nbrowser_*`) | The task | Tool |
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.711"+version = "2.0.712" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.711"+version = "2.0.712" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.711"+version = "2.0.712" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-core", "pup-host",@@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.711"+version = "2.0.712" dependencies = [ "fontdue", "image",@@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-core", "serde",@@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.711"+version = "2.0.712" dependencies = [ "pup-core", "pup-host",@@ -757,7 +757,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.711"+version = "2.0.712" dependencies = [ "base64", "png 0.17.16",
rust/Cargo.toml+1−1@@ -4,7 +4,7 @@ members = ["core", "host", "host-win", "host-mac", "cdp", "browser", "session", # One version for the whole bridge, in lockstep with src/BRIDGE_VERSION (tools/ship.sh bumps both). [workspace.package]-version = "2.0.711"+version = "2.0.712" edition = "2021" license = "MIT"
rust/verbs/src/lib.rs+38−1@@ -842,7 +842,44 @@ pub fn session_for_tab_url(ctx: &Ctx, url: &str) -> Option<(Session, Tab)> { /// tab, reload, back and forward painted nothing (John, 2026-10-05). A window with no handle yet (being born) /// gets no bar; start_icon_apply covers its first seconds. fn with_bar(ctx: &Ctx, caller: &Caller, args: &Value, op: &str, work: impl FnOnce() -> Value) -> Value {- match resolve(ctx, caller, args) { Ok(s) => activity::with_progress(ctx, &s.id, op, work), Err(_) => work() }+ let r = match resolve(ctx, caller, args) { Ok(s) => activity::with_progress(ctx, &s.id, op, work), Err(_) => work() };+ wall_check(ctx, caller, args, r)+}++/// 2.0.712: say when a page load landed on a bot wall, and warn before a thread trips one. A Cloudflare challenge+/// page used to come back as an ordinary successful load, so a thread kept loading DigiKey pages 7 s apart and dug+/// the wall deeper (John's ECAS window, 2026-10-11). Signatures come from the electronics-sites skillpack.+const BOT_WALL_JS: &str = r#"(()=>{const t=(document.title||'').slice(0,200);const b=((document.body&&document.body.innerText)||'').slice(0,3000);const short=b.length<1500;+if(/just a moment|performing security verification|checking your browser/i.test(t)||(short&&/performing security verification|verify you are human/i.test(b))||document.querySelector('iframe[src*="challenges.cloudflare.com"]'))return 'Cloudflare';+if(/access to this page has been denied/i.test(t+' '+b)||/press (&|and) hold/i.test(b)||document.querySelector('#px-captcha,[id^="px-captcha"]'))return 'PerimeterX';+if(/^access denied/i.test(t)&&short)return 'Akamai';+if(document.querySelector('iframe[src*="captcha-delivery.com"]'))return 'DataDome';+if(short&&/verify you are a human|are you a robot|unusual traffic/i.test(b))return 'bot check';+return ''})()"#;+const PACED_HOSTS: [&str; 8] = ["digikey.", "mouser.", "arrow.com", "ti.com", "analog.com", "st.com", "octopart.com", "newark.com"];+static LAST_LOAD: std::sync::Mutex<Option<HashMap<String, u64>>> = std::sync::Mutex::new(None);++fn wall_check(ctx: &Ctx, caller: &Caller, args: &Value, mut r: Value) -> Value {+ if r["ok"] == json!(false) || r["success"] == json!(false) { return r; }+ let Ok(s) = resolve(ctx, caller, args) else { return r };+ let Some(t) = s.active().cloned() else { return r };+ let host = t.url.split("//").nth(1).unwrap_or("").split('/').next().unwrap_or("").to_lowercase();+ // pacing: a second load on a walled vendor within 15 s, by any thread on this desktop+ if let Some(key) = PACED_HOSTS.iter().find(|h| host.contains(**h)) {+ let now = pup_core::now_ms();+ let mut g = LAST_LOAD.lock().unwrap(); let m = g.get_or_insert_with(HashMap::new);+ if let Some(prev) = m.insert(key.to_string(), now) {+ let gap = now.saturating_sub(prev);+ if gap < 15_000 { r["_paceWarning"] = json!(format!("{host} was loaded {} s ago. This site walls automated browsers that load pages in quick succession; wait at least 15 s between pages here, and get stock, price and datasheets through adom-parts-search instead of loading product pages.", gap / 1000)); }+ }+ }+ let Ok(lane) = transport::lane_for(ctx, &s) else { return r };+ let vendor = transport::evaluate(&lane, &t, BOT_WALL_JS).ok().and_then(|v| v.as_str().map(|x| x.to_string()).or_else(|| v["value"].as_str().map(|x| x.to_string()))).unwrap_or_default();+ if vendor.is_empty() { return r; }+ pup_core::log(format!("[wall] \"{}\": {host} answered with a {vendor} bot check ({})", s.id, t.url.chars().take(100).collect::<String>()));+ r["botWall"] = json!({ "vendor": vendor, "host": host, "url": t.url });+ r["_hint"] = json!(format!("BOT WALL: {host} answered with a {vendor} bot check instead of the page. Do NOT reload or load more pages from {host} now; that makes it worse. For parts data (stock, price, datasheet) use adom-parts-search. If you need this page itself: ask the user for ONE click on the check in this window (pup_alert_window or pup_raise_os_window with a reason), or open it in the user's own browser with nb. Never try to solve the check yourself."));+ r } pub fn resolve(ctx: &Ctx, caller: &Caller, args: &Value) -> Result<Session, Value> {