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
WHY-CFT: prove reasons 1, 2 and 4 with measured screenshots (CfT 148 vs branded 151), correct a claim that did not survive testing, and add Widevine, Puppeteer-vs-Playwright with naming analysis, and Firefox sections
6 files changed
+292−23
docs/WHY-CFT.md+292−23@@ -9,23 +9,100 @@ already on the machine" to make first runs instant (no ~160 MB download when a p Chrome or Edge is already installed). Running that way in production surfaced the problems below, and on 2026-08-10 the policy flipped back to CfT for good. -## 1. The automation banner you cannot turn off+--- -The moment pup does real automation in branded Chrome, moving a synthetic cursor, filling a login-form, assisting with a captcha, Chrome asserts the infobar: **"Chrome is being controlled by-automated test software."** It blips in and out as automation starts and stops, reflowing the-viewport each time, and it is deliberately hard to suppress in the branded build: the classic-trick (stripping `--enable-automation` from the launch args) changes input and focus semantics,-and newer branded builds re-assert automation indicators when DevTools attaches regardless. Users-watched the banner flicker over their windows all day. CfT is the build Google ships FOR-automation: the banner is not fighting you there, and suppressing chrome-level test UI is-supported behavior rather than an arms race.+## How the claims below were measured (2026-08-25) -## 2. Extensions: branded Chrome closed every silent path (the forcing reason)+This page used to assert its reasons. It now proves them. Every screenshot and result below comes+from one controlled comparison run on AdomLapper: -Branded Chrome 137 (mid-2025) **removed the `--load-extension` flag** specifically to stop-software from silently side-loading extensions. We verified the fallout live on branded 148-(2026-08-10), trying every route to auto-install the Adom Pup toolbar:+| | |+|---|---|+| Chrome for Testing | **148.0.7778.97** (`C:\Users\john\.cache\puppeteer\chrome\win64-148.0.7778.97`) |+| Branded Chrome | **151.0.7922.170** (`C:\Program Files\Google\Chrome\Application`) |+| Method | identical flags, a FRESH throwaway `--user-data-dir` per launch, same URL, only the binary differs |+| Evidence | the browser's own CDP target list (`/json/list`) plus window screenshots |++**One long-standing claim on this page did not survive that test and has been corrected.** See the+box in section 1. If you are here because someone quoted the old version at you, they were quoting+something we had never actually verified.++---++## 1. The automation banner (real, but for a narrower reason than we used to claim)++When a browser is launched with `--enable-automation`, branded Chrome asserts an infobar reading+**"Chrome is being controlled by automated test software."** Puppeteer passes that flag BY DEFAULT,+so any naive puppeteer script against branded Chrome wears the banner. It blips in and out as+automation starts and stops, reflowing the viewport each time.++++Chrome for Testing, given the **exact same flag**, does not show it:++++> ### CORRECTION (2026-08-25)+>+> This page previously claimed that "newer branded builds re-assert automation indicators when+> DevTools attaches regardless." **That is false on Chrome 151, and it was never measured.**+>+> Tested directly: branded Chrome launched WITHOUT `--enable-automation`, then a real CDP+> WebSocket session attached (`Page.enable`, `Runtime.enable`, `Runtime.evaluate`). No banner+> appeared, and `navigator.webdriver` was `false`.+>+> +>+> The banner is keyed on the FLAG, not on the attach. So the honest version of this reason is:+> puppeteer's default launch triggers it, and **pup strips the flag** (`ignoreDefaultArgs:+> ['--enable-automation']`), which suppresses it on either binary. Section 1 is therefore a reason+> to strip a flag, NOT on its own a reason to choose CfT. The forcing reason is section 2.++Two smaller notes that came out of the same run, in the interest of not overselling CfT:++- **Raw CfT is not banner-free either.** It carries its own permanent notice ("Chrome for Testing+ v148 is only for automated testing. For regular browsing, use a standard version of Chrome"), plus+ a "Sign in to Chromium?" button. Your pup windows do not show these because pup passes+ `--disable-infobars`. The accurate statement is "pup's flag set produces a clean window", not+ "CfT is clean out of the box".+- `--disable-blink-features=AutomationControlled` is NOT needed and was removed in 1.9.159: that+ blink feature only activates via `--enable-automation`, which pup never passes.++## 2. Extensions: branded Chrome closed every silent path (THE forcing reason)++Branded Chrome 137 (mid-2025) **removed the `--load-extension` flag** specifically to stop software+from silently side-loading extensions.++This is the decisive experiment. Both browsers, the same flag, the same directory+(`--load-extension=C:\Users\john\.adom\pup-extension`):++**Chrome for Testing 148: the extension loads.** Its service worker is running, visible in the+browser's own target list:++```+type title url+page Example Domain https://example.com/+service_worker Service Worker chrome-extension://liomcalad.../worker.js chrome-extension://liomcalad.../worker.js+```++++**Branded Chrome 151: the extension is not there at all.** Same flag, same directory, five targets,+and none of them is ours (the two extension entries are Google's OWN built-ins, Hangouts among them,+which is incidentally evidence for section 4):++```+background_page Google Hangouts chrome-extension://nkeimhogjdpnpccoofpliimaahmaaome/background.html+page Example Domain https://example.com/+browser_ui Omnibox Popup chrome://omnibox-popup.top-chrome/+browser_ui Omnibox Popup chrome://omnibox-popup.top-chrome/omnibox_popup_aim.html+service_worker Service Worker chrome-extension://fignfifoniblkonapihmkfakmlgkbkcf/service_worker.js+```++++We verified the fallout live on branded 148 back on 2026-08-10, trying every route to auto-install+the Adom Pup toolbar: - The CDP replacement (`Extensions.loadUnpacked`) requires a `--remote-debugging-pipe` launch, which is incompatible with pup's detached-launch architecture (pup windows deliberately outlive@@ -38,7 +115,8 @@ software from silently side-loading extensions. We verified the fallout live on CfT still honors `--load-extension`. So on CfT the toolbar is simply a **launch argument**: every profile launch carries it, every window is born with the toolbar pinned, and there is no install-step in existence. This capability gap alone justifies the policy.+step in existence. **This capability gap alone justifies the policy**, and unlike section 1 it+cannot be worked around with a flag. ## 3. Version determinism @@ -47,13 +125,26 @@ Branded Chrome updates itself underneath running profiles on Google's schedule, behavior can drift mid-session, and two pup windows opened an hour apart can be driving different browser versions. Deterministic versions are what make pup's ralph tests reproducible and make a CDP bug a fixable fact instead of a moving target. pup owns CfT's update cadence via its own-self-heal (src/chrome.js), which is exactly where that control belongs.+self-heal (`src/chrome.js`), which is exactly where that control belongs. -## 4. None of the consumer chrome+Incidental confirmation from the run above: the machine had branded Chrome **151.0.7922.170** AND a+second version **151.0.7922.174** staged in its install directory at the same moment. That is the+auto-updater mid-flight, and it is precisely the moving target this section is about. -No first-run bubbles, no default-browser nagging, no profile-import prompts, no sign-in and sync-pressure, and no Finch field-trial experiments silently flipping browser behavior per machine.-Branded Chrome runs server-assigned experiments; CfT gives every machine the same browser.+## 4. None of the consumer chrome (true, with an honest caveat)++No first-run bubbles, no default-browser nagging, no profile-import prompts, and no Finch+field-trial experiments silently flipping browser behavior per machine. Branded Chrome runs+server-assigned experiments; CfT gives every machine the same browser.++Measured: a FRESH branded profile came up with Google's own extensions already loaded (the Hangouts+background page and a component service worker, listed in section 2). A fresh CfT profile had only+the page under test and whatever pup loaded on purpose.++**The caveat, stated plainly:** CfT is not free of consumer chrome either. It shows "Sign in to+Chromium?" and its own automated-testing notice. pup suppresses those with `--disable-infobars`. The+difference is that on CfT the suppression flags WORK and stay working, whereas on branded Chrome the+equivalent surfaces are either unsuppressible or re-asserted by policy and Finch. ## 5. Isolation from the user's own browser @@ -61,16 +152,194 @@ A different binary means a different process tree: the user's real Chrome can up closed without touching a single pup window, and pup's taskbar identity never tangles with their browsing. Profiles were always pup-owned; CfT extends the separation to the binary itself. +---++## Widevine: why protected video does not play, and whether CfT could gain it++This is the most-asked question about the policy, and the answer is worse than "not yet".++### Measured++| Browser | `com.widevine.alpha` | `org.w3.clearkey` |+|---|---|---|+| Chrome for Testing 148 | **NotSupportedError** | OK |+| Branded Chrome 151 | **supported** | OK |++`requestMediaKeySystemAccess` for Widevine is refused on CfT and granted on branded Chrome.++### Can we just add the CDM? No, and we tested it rather than guessing++CfT ships no `WidevineCdm` directory anywhere in its install tree. Branded Chrome has a real one:++```+C:\Program Files\Google\Chrome\Application\151.0.7922.170\WidevineCdm\manifest.json+C:\Program Files\Google\Chrome\Application\151.0.7922.170\WidevineCdm\LICENSE+C:\Program Files\Google\Chrome\Application\151.0.7922.170\WidevineCdm\_platform_specific\win_x64\widevinecdm.dll+C:\Program Files\Google\Chrome\Application\151.0.7922.170\WidevineCdm\_platform_specific\win_x64\widevinecdm.dll.sig+```++We copied that entire directory (the real DLL, its manifest, and its signature) into a throwaway+CfT profile and relaunched. Result: **still `NotSupportedError`**.++The ClearKey row in the table above is what explains it. **EME itself is fully present in CfT** (the+generic key-system API works), and only the Widevine integration is missing. So this is not a+missing file that we can supply, it is a build-time exclusion of the Widevine CDM host. Dropping+binaries in cannot fix it, which is why the attempt above failed. The `.sig` file is a further hint:+the CDM is signature-verified, so even a build with the host enabled would need a CDM that Google+licensed to that build.++Adding Widevine to CfT would require a Chromium build with Widevine enabled AND a CDM license from+Google (the Widevine partner program). That is not something pup can do from this repo, and+redistributing the CDM ourselves is not permitted.++### So how does anybody test Widevine?++You drive a browser that HAS it. Concretely, in Adom terms:++1. **nb (the Adom browser extension)** drives the user's REAL signed-in Chrome or Edge, which has+ Widevine and the user's DRM-bound identity. This is the right answer for anything involving a+ real streaming account.+2. **pup pinned to branded Chrome**: `pup_use {browser:"chrome"}`. Everything keeps working except+ the toolbar extension (section 2) and you inherit the update-drift of section 3. This is the+ supported escape hatch, and it is exactly what the trade-off list below means by "users who+ explicitly pin branded Chrome".+3. Commercial device farms (BrowserStack and similar) run real branded browsers for the same reason.++The industry answer is the same as ours: **DRM is tested on the shipping browser, not on the+automation build.** CfT is not an oversight here, it is the automation build behaving as designed.++---++## Why Puppeteer and not Playwright (and whether "pup" is a lie)++The office challenge is: "we are not really using Puppeteer, we just drive CDP directly, so the name+is false." That is a fair thing to suspect from reading the code, but **the counts do not support+it.** Measured across `src/server.js`:++| Puppeteer high-level API | calls | | Raw CDP | calls |+|---|---|---|---|---|+| `.evaluate(` | 85 | | `createCDPSession` | 27 |+| `.url(` | 67 | | `.send('Domain.method')` | 55 |+| `.target(` | 28 | | | |+| `.close(` | 25 | | **by domain** | |+| `.pages(` | 18 | | `Browser.*` | 35 |+| `.click(` | 16 | | `Page.*` | 9 |+| `.goto(` | 13 | | `Debugger.*` | 4 |+| `.newPage(` | 10 | | `Runtime.*` | 2 |+| `.title(` | 9 | | `Network.*` | 2 |+| `.type(` | 7 | | `IO.*` | 2 |+| `.screenshot(` | 5 | | `Target.*` | 1 |+| `.setViewport(` / `.pdf(` | 5 | | | |+| **~288 total** | | | **~82 total** | |++Roughly three quarters of the driving goes through Puppeteer's own API. The raw CDP is concentrated+in `Browser.*` (35 of 55 sends), which is window bounds and window-state work that Puppeteer+deliberately does not expose. That is the normal way to use the library, not a bypass of it.+Puppeteer is also load-bearing in three places nothing else touches: `puppeteer.connect` (16 sites,+the re-adoption of detached windows over the debugging port), `puppeteer.launch`, and+`puppeteer.executablePath` (the pinned-CfT resolution in `src/chrome.js`).++### The case for Puppeteer over Playwright, honestly++- **pup is deliberately Chrome-only.** Playwright's headline feature is one API across Chromium,+ Firefox and WebKit. If you have committed to CfT for the reasons above, that abstraction buys+ nothing and costs a layer.+- **Detached windows are the architecture.** pup windows outlive the bridge process and are+ re-adopted by connecting to the debugging port. `puppeteer.connect({browserURL})` supports that+ as a first-class flow. Playwright's `connectOverCDP` exists, but its model assumes it owns the+ browser lifecycle, and pup's whole recovery story is that it does not.+- **One update axis.** Puppeteer pins a CfT buildId and `@puppeteer/browsers` installs exactly that.+ Playwright ships its own patched browser builds plus a driver process, which is a second thing to+ version, self-heal and reason about.+- **Where Playwright would genuinely be better:** cross-browser work, and its auto-waiting and+ locator model, which are nicer than hand-rolled waits. This is not hypothetical inside Adom:+ `adom/adom-desktop-playwright-bridge` is a sibling bridge built on Playwright. If you want the+ Playwright argument made properly, that is the team to ask, and this section should not pretend+ the choice is obvious for every project. It is obvious for THIS project's constraints.++### Is the name "pup" wrong?++Two separate questions, and they deserve separate answers.++**"Is the name factually false?" No.** The library is a real dependency doing most of the work, as+measured above. Anyone who says pup does not use Puppeteer is mistaken.++**"Is the name a good idea?" That is the better question, and the answer does not depend on the+library at all.** The recommendation is: **keep the name, and stop justifying it by the dependency.**++- The name is already load-bearing far beyond this repo. It is a taught user vocabulary alongside+ **nb** (native browser) and **wv** (webview), it is the wiki slug `adom/pup-bridge`, the dock card,+ the taskbar identity, the `dock:pup-bridge` thread tag, the settings keys, and the say-strip+ phrases users are taught to speak out loud ("open my app in pup"). Renaming costs all of that and+ buys a purity that users do not perceive.+- The real fix for the complaint is definitional, not lexical. **"pup" is the noun for the AI-driven+ browser surface**, in the same family as nb and wv. It is not an abbreviation of a library name.+ Define it that way (the `definitions` skill already does) and the name stays true even if the+ driver is swapped for something else tomorrow, which is exactly the failure mode the office is+ worried about.+- If we ever DID want the name to track the implementation, the honest options are ugly:+ "cdp-bridge" (accurate, unspeakable, and wrong the day we add Firefox via BiDi) or "chrome-bridge"+ (wrong the day we drive anything else). Both are worse than a stable noun.++So: the complaint is worth answering, the answer is that the premise is wrong on the numbers, and+the naming policy should be written down so it stops coming up.++---++## Driving Firefox++There is real demand for this and it is not crazy: **the toolchain already supports it.** The pinned+`@puppeteer/browsers` enumerates Firefox as a first-class browser:++```+{"CHROME":"chrome","CHROMEHEADLESSSHELL":"chrome-headless-shell","CHROMIUM":"chromium",+ "FIREFOX":"firefox","CHROMEDRIVER":"chromedriver"}+```++So "can Puppeteer drive Firefox" is settled: yes, over **WebDriver BiDi** (Firefox removed its CDP+implementation in favour of the standard). The work is not in the driving, it is in everything pup+has built ON TOP of Chrome-specific behaviour. Honest inventory, worst first:++1. **Window management is the big one.** 35 of pup's 55 raw CDP sends are `Browser.*`, mostly window+ bounds and state. BiDi has no equivalent surface, so every one of those needs a different path+ (most likely straight Win32 through `src/win32.js`, which pup already owns).+2. **Windows taskbar identity assumes Chrome.** `win32.js` finds windows by the window class+ `Chrome_WidgetWin_1`. Firefox uses a different class, so thread tiles, AUMID stamping, jump lists+ and overlay badges all need the class match generalised before any of them work.+3. **The toolbar extension does not port.** It is MV3 loaded via `--load-extension`, which Firefox+ does not have. Firefox needs a WebExtension packaged as a signed XPI, or a debugging-session+ install, so section 2's entire "no install step in existence" property is lost on Firefox.+4. **Profiles and the credential vault** assume a Chrome user-data-dir layout. Firefox profiles are+ a different shape, so the shared adom-you story needs a second implementation.+5. **No Widevine there either**, for the record: Firefox uses Google's CDM under its own license, and+ a fresh automation profile will not have it enabled by default.++**Realistic staging, if we decide to do it:**++- **Phase 1, read-only Firefox.** `pup_open_window {browser:"firefox"}` giving navigate, screenshot,+ eval and record over BiDi. No taskbar identity, no extension, no credential vault. This is enough+ for "does my app render in Firefox", which is what most of the requests actually want.+- **Phase 2, window identity.** Generalise the `win32.js` class match so Firefox windows can be+ found, positioned, and given taskbar identity. This is where the off-screen birth and the+ never-steal-focus contract get re-earned rather than assumed.+- **Phase 3, extension parity.** Only if the toolbar genuinely matters on Firefox, and knowing it+ needs a signing pipeline.++Phase 1 is a contained piece of work. Phases 2 and 3 are each comparable to a meaningful slice of+what pup already is, so they should be driven by real demand rather than symmetry.++---+ ## Known trade-offs, stated honestly - **First-run download** (~160 MB) on machines without CfT cached. pup has detected, installed, and repaired CfT by itself since long before this policy (readiness self-heal). - The window title and Task Manager say **"Google Chrome for Testing"**. The rendered window is otherwise identical.-- **No Widevine DRM**: protected video (streaming services) will not play in pup. Driving those is- an nb/nbe job anyway, with the user's real browser and identity.+- **No Widevine DRM**: protected video will not play in pup, it cannot be added by supplying files+ (measured above), and driving DRM is an nb job with the user's real browser and identity. - Users who explicitly pin branded Chrome (`pup_use {browser:"chrome"}`) keep everything working- except the toolbar extension and infobar suppression; pup tells them why when it matters.+ except the toolbar extension and the section 1 flag hygiene; pup tells them why when it matters. ## What did NOT change with the switch
docs/img/why-cft/banner-branded-cdp.pngadded⋯ 1 unchanged line ⋯
docs/img/why-cft/banner-branded.pngadded⋯ 1 unchanged line ⋯
docs/img/why-cft/banner-cft.pngadded⋯ 1 unchanged line ⋯
docs/img/why-cft/ext-branded.pngadded⋯ 1 unchanged line ⋯
docs/img/why-cft/ext-cft.pngadded⋯ 1 unchanged line ⋯