chore(release): 0.27.0-fork.29 — plugin overflowTest fix, popup-free auto-connect, ab-connect rebrand+icon, README
Release binaries / Build macOS ARM64 (push) Has been cancelled
Release binaries / Build macOS x64 (push) Has been cancelled
Release binaries / Build Linux ARM64 (push) Has been cancelled
Release binaries / Build Linux musl ARM64 (push) Has been cancelled
Release binaries / Build Linux musl x64 (push) Has been cancelled
Release binaries / Build Linux x64 (push) Has been cancelled
Release binaries / Build Windows x64 (push) Has been cancelled
Release binaries / Attach binaries to GitHub Release (push) Has been cancelled
- stealth(plugins): stop overwriting real native navigator.plugins in headed mode (the JS fake had a non-native item(), broken uint32 wrap → incolumitas overflowTest FAIL, and an anachronistic Native Client plugin). Leave native plugins untouched when present; modernize the headless-escape fallback to the real 5 PDF-viewer set with masked-native item()/namedItem(). - connect: auto_connect_cdp() now prefers the dialog-free ab-connect relay over the raw :9222 CDP port, so Chrome 136+'s "Allow remote debugging?" consent modal no longer fires when the extension relay is live. Gated by a bare-TCP relay_is_live() probe (+3 unit tests). - extension: rename ab-connect to "agent-browser-stealth" + new stealth icon set (16/32/48/128). - docs(README): hero/shield/fingerprint images, expanded detector results (CreepJS 0% stealth, incolumitas all-OK, BrowserScan CDP-clean), and a "Verify it yourself" section. .gitignore: allow assets/ + extension icons.
@@ -38,6 +38,10 @@ __pycache__/
|
|||||||
*.webm
|
*.webm
|
||||||
test/e2e/.dogfood-output/
|
test/e2e/.dogfood-output/
|
||||||
|
|
||||||
|
# ...but these are real repo assets, not test artifacts — keep them tracked
|
||||||
|
!assets/*.png
|
||||||
|
!extensions/ab-connect/icons/*.png
|
||||||
|
|
||||||
# Package manager
|
# Package manager
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
# agent-browser-stealth
|
# agent-browser-stealth
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Stealth fork of [agent-browser](https://github.com/vercel-labs/agent-browser) — connects to your real Chrome, shares your login sessions, and is undetectable by anti-bot systems.
|
Stealth fork of [agent-browser](https://github.com/vercel-labs/agent-browser) — connects to your real Chrome, shares your login sessions, and is undetectable by anti-bot systems.
|
||||||
|
|
||||||
For basic usage, commands, and API reference, see the [upstream documentation](https://github.com/vercel-labs/agent-browser).
|
For basic usage, commands, and API reference, see the [upstream documentation](https://github.com/vercel-labs/agent-browser).
|
||||||
|
|
||||||
## Why this fork?
|
## Why this fork?
|
||||||
|
|
||||||
|
<img src="assets/fingerprint.png" alt="real but undetectable fingerprint" width="300" align="right" />
|
||||||
|
|
||||||
**agent-browser** launches a fresh browser with an empty profile. You need to log in again, and websites can detect it's automated.
|
**agent-browser** launches a fresh browser with an empty profile. You need to log in again, and websites can detect it's automated.
|
||||||
|
|
||||||
**agent-browser-stealth** connects to your existing Chrome. Your cookies, sessions, and browser fingerprint are all real — because it IS your real browser.
|
**agent-browser-stealth** connects to your existing Chrome. Your cookies, sessions, and browser fingerprint are all real — because it IS your real browser.
|
||||||
@@ -115,6 +119,8 @@ In CI environments, standalone mode is used automatically.
|
|||||||
|
|
||||||
## Anti-detection
|
## Anti-detection
|
||||||
|
|
||||||
|
<img src="assets/shield.png" alt="stealth shield" width="320" align="right" />
|
||||||
|
|
||||||
When connected to your real Chrome, we inject **zero** JavaScript patches. Your browser's fingerprint is completely genuine. The guiding rule is **native CDP/Chrome overrides over JS lies** — a re-defined getter is itself detectable; a native override isn't.
|
When connected to your real Chrome, we inject **zero** JavaScript patches. Your browser's fingerprint is completely genuine. The guiding rule is **native CDP/Chrome overrides over JS lies** — a re-defined getter is itself detectable; a native override isn't.
|
||||||
|
|
||||||
- `navigator.webdriver = false` via `Emulation.setAutomationOverride` (native, undetectable by CreepJS-style lie tests).
|
- `navigator.webdriver = false` via `Emulation.setAutomationOverride` (native, undetectable by CreepJS-style lie tests).
|
||||||
@@ -124,11 +130,27 @@ When connected to your real Chrome, we inject **zero** JavaScript patches. Your
|
|||||||
|
|
||||||
| Test site | Result |
|
| Test site | Result |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [CreepJS](https://abrahamjuliot.github.io/creepjs/) | 0% stealth, 0% headless |
|
| [CreepJS](https://abrahamjuliot.github.io/creepjs/) | **0% stealth · 0% headless** (no override traces at all) |
|
||||||
| [bot.sannysoft.com](https://bot.sannysoft.com) | All green |
|
| [bot.incolumitas.com](https://bot.incolumitas.com/) | all checks OK — `overflowTest`, `overrideTest`, `puppeteerExtraStealthUsed`, worker consistency |
|
||||||
| [Cloudflare Turnstile](https://nowsecure.nl) | Passed |
|
| [bot.sannysoft.com](https://bot.sannysoft.com) | all green |
|
||||||
|
| [BrowserScan](https://www.browserscan.net/bot-detection) | Webdriver · User-Agent · CDP all clean |
|
||||||
|
| [Cloudflare Turnstile](https://nowsecure.nl) | passed |
|
||||||
|
|
||||||
When using `--launch` mode (standalone browser), a full suite of 32 stealth patches is applied for headless Chrome.
|
`0% stealth` on CreepJS is the key number: because the connect path patches **nothing**, there is no override for a lie-detector to catch. (Dashboards that read `navigator.languages` order or IP geolocation may show a soft "navigator"/"location" flag — that tracks *your real Chrome's* language list and network, not an automation tell.)
|
||||||
|
|
||||||
|
When using `--launch` mode (standalone browser), a full suite of stealth patches is applied instead, and it still passes the suite above.
|
||||||
|
|
||||||
|
### Verify it yourself
|
||||||
|
|
||||||
|
Don't take our word for it — point your connected Chrome at the toughest public detectors and compare:
|
||||||
|
|
||||||
|
- **[CreepJS](https://abrahamjuliot.github.io/creepjs/)** — the most thorough fingerprint / lie detector
|
||||||
|
- **[bot.incolumitas.com](https://bot.incolumitas.com/)** — behavioral + fingerprint scoring with a public methodology
|
||||||
|
- **[BrowserScan](https://www.browserscan.net/bot-detection)** — Webdriver / User-Agent / CDP / Navigator
|
||||||
|
- **[bot.sannysoft.com](https://bot.sannysoft.com)** — the classic automation-marker checklist
|
||||||
|
- **[pixelscan.net](https://pixelscan.net/)** · **[iphey.com](https://iphey.com/)** — consistency & identity
|
||||||
|
|
||||||
|
We deliberately **don't ship our own bot detector** — the strongest, most honest benchmark is the market's best detectors run against your real browser.
|
||||||
|
|
||||||
### Tuning knobs (environment variables)
|
### Tuning knobs (environment variables)
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
@@ -45,7 +45,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agent-browser-stealth"
|
name = "agent-browser-stealth"
|
||||||
version = "0.27.0-fork.28"
|
version = "0.27.0-fork.29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "agent-browser-stealth"
|
name = "agent-browser-stealth"
|
||||||
version = "0.27.0-fork.28"
|
version = "0.27.0-fork.29"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Fast browser automation CLI for AI agents"
|
description = "Fast browser automation CLI for AI agents"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -773,6 +773,26 @@ fn running_process_cmdlines() -> Option<Vec<String>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auto_connect_cdp() -> Result<String, String> {
|
pub async fn auto_connect_cdp() -> Result<String, String> {
|
||||||
|
// Prefer the dialog-free `ab-connect` extension relay when it is live.
|
||||||
|
// The relay drives the user's REAL Chrome via the extension's
|
||||||
|
// `chrome.debugger` permission, which — unlike a raw `--remote-debugging-port`
|
||||||
|
// CDP attach — never triggers Chrome 136+'s per-connection
|
||||||
|
// "Allow remote debugging?" consent modal. The native-messaging host writes
|
||||||
|
// ~/.agent-browser/relay-cdp-url while connected and removes it on exit, so a
|
||||||
|
// present URL means the relay is up. This must win over the DevToolsActivePort
|
||||||
|
// / :9222 probes below: if the user's Chrome happens to also be listening on a
|
||||||
|
// debug port, attaching there would pop the consent dialog and defeat the
|
||||||
|
// whole zero-interaction extension path.
|
||||||
|
if let Some(relay) = crate::connect::relay_url() {
|
||||||
|
// The relay is a local CDP-over-WS endpoint we connect to like Chrome.
|
||||||
|
// A bare TCP liveness check (no WS upgrade) confirms it is actually
|
||||||
|
// accepting before we commit, mirroring the consent-free probe used for
|
||||||
|
// DevToolsActivePort.
|
||||||
|
if relay_is_live(&relay).await {
|
||||||
|
return Ok(relay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let user_data_dirs = get_chrome_user_data_dirs();
|
let user_data_dirs = get_chrome_user_data_dirs();
|
||||||
|
|
||||||
for dir in &user_data_dirs {
|
for dir in &user_data_dirs {
|
||||||
@@ -850,6 +870,22 @@ async fn tcp_port_alive(port: u16) -> bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consent-free liveness for the `ab-connect` relay ws URL (`ws://127.0.0.1:<port>/…`).
|
||||||
|
/// Parses the port and does a bare TCP connect — a stale relay-cdp-url file
|
||||||
|
/// (host exited without cleanup) must not divert auto-connect away from the
|
||||||
|
/// working port path.
|
||||||
|
async fn relay_is_live(ws_url: &str) -> bool {
|
||||||
|
let port = ws_url
|
||||||
|
.strip_prefix("ws://")
|
||||||
|
.and_then(|rest| rest.split('/').next())
|
||||||
|
.and_then(|hostport| hostport.rsplit(':').next())
|
||||||
|
.and_then(|p| p.parse::<u16>().ok());
|
||||||
|
match port {
|
||||||
|
Some(p) => tcp_port_alive(p).await,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the default Chrome user-data directory paths for the current platform.
|
/// Returns the default Chrome user-data directory paths for the current platform.
|
||||||
/// Includes Chrome, Chrome Canary, Chromium, and Brave.
|
/// Includes Chrome, Chrome Canary, Chromium, and Brave.
|
||||||
pub fn get_chrome_user_data_dirs() -> Vec<PathBuf> {
|
pub fn get_chrome_user_data_dirs() -> Vec<PathBuf> {
|
||||||
@@ -2200,4 +2236,35 @@ mod tests {
|
|||||||
let result = resolve_cdp_from_active_port(port, "/devtools/browser/dead").await;
|
let result = resolve_cdp_from_active_port(port, "/devtools/browser/dead").await;
|
||||||
assert!(result.is_err(), "should fail when nothing is listening");
|
assert!(result.is_err(), "should fail when nothing is listening");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_relay_is_live_true_when_listening() {
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let port = listener.local_addr().unwrap().port();
|
||||||
|
let url = format!("ws://127.0.0.1:{}/abc-guid", port);
|
||||||
|
assert!(
|
||||||
|
relay_is_live(&url).await,
|
||||||
|
"relay_is_live should be true while the port is accepting"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_relay_is_live_false_when_dead() {
|
||||||
|
// Bind to grab a free port, then drop so nothing is listening.
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||||
|
let port = listener.local_addr().unwrap().port();
|
||||||
|
drop(listener);
|
||||||
|
let url = format!("ws://127.0.0.1:{}/abc-guid", port);
|
||||||
|
assert!(
|
||||||
|
!relay_is_live(&url).await,
|
||||||
|
"relay_is_live must be false for a stale relay-cdp-url (host exited)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_relay_is_live_false_on_malformed_url() {
|
||||||
|
assert!(!relay_is_live("not-a-ws-url").await);
|
||||||
|
assert!(!relay_is_live("ws://127.0.0.1/no-port").await);
|
||||||
|
assert!(!relay_is_live("ws://127.0.0.1:notaport/x").await);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,24 @@ const __abStealth = { locale: "en-US", languages: ["en-US", "en"], allowWebGLCon
|
|||||||
defineVendor(navigator);
|
defineVendor(navigator);
|
||||||
})();
|
})();
|
||||||
(function(){
|
(function(){
|
||||||
|
// Native > JS lies: a real headed Chrome already exposes the correct, fully
|
||||||
|
// native navigator.plugins (5 PDF-viewer aliases, a native item() that does
|
||||||
|
// the WebIDL uint32-index wrap, length on the prototype). Overriding that
|
||||||
|
// with a JS fake is strictly worse — it ships a non-native item() whose
|
||||||
|
// .toString() reveals the patch, breaks the uint32 wrap (incolumitas
|
||||||
|
// overflowTest), and pins an anachronistic "Native Client" plugin that modern
|
||||||
|
// Chrome removed. Since this fork forbids headless and always launches headed,
|
||||||
|
// the native plugins are present, so we leave them alone. We only fall back to
|
||||||
|
// a synthetic list when native plugins are genuinely empty (e.g. the
|
||||||
|
// discouraged AGENT_BROWSER_ALLOW_HEADLESS escape on old headless).
|
||||||
|
try {
|
||||||
|
const np = navigator.plugins;
|
||||||
|
const itemNative =
|
||||||
|
np && typeof np.item === 'function' &&
|
||||||
|
/\[native code\]/.test(Function.prototype.toString.call(np.item));
|
||||||
|
if (np && np.length > 0 && itemNative) return;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
const makeMimeType = (type, suffixes, description) => {
|
const makeMimeType = (type, suffixes, description) => {
|
||||||
const mime = Object.create(MimeType.prototype);
|
const mime = Object.create(MimeType.prototype);
|
||||||
Object.defineProperties(mime, {
|
Object.defineProperties(mime, {
|
||||||
@@ -427,40 +445,54 @@ const __abStealth = { locale: "en-US", languages: ["en-US", "en"], allowWebGLCon
|
|||||||
return plugin;
|
return plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Make a fake method masquerade as native: name + `[native code]` toString.
|
||||||
|
const maskNative = (fn, name) => {
|
||||||
|
Object.defineProperty(fn, 'name', { value: name, configurable: true });
|
||||||
|
Object.defineProperty(fn, 'toString', {
|
||||||
|
value: () => `function ${name}() { [native code] }`,
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
return fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modern Chrome (since ~v109) exposes exactly these 5 PDF-viewer aliases and
|
||||||
|
// two mimeTypes (application/pdf, text/pdf). Native Client was removed years
|
||||||
|
// ago, so it must NOT appear. Each plugin carries both mimeTypes.
|
||||||
const pdfMime = makeMimeType('application/pdf', 'pdf', 'Portable Document Format');
|
const pdfMime = makeMimeType('application/pdf', 'pdf', 'Portable Document Format');
|
||||||
const chromePdfMime = makeMimeType(
|
const textPdfMime = makeMimeType('text/pdf', 'pdf', 'Portable Document Format');
|
||||||
'application/x-google-chrome-pdf',
|
const mimes = [pdfMime, textPdfMime];
|
||||||
'pdf',
|
|
||||||
'Portable Document Format'
|
|
||||||
);
|
|
||||||
const naclMime = makeMimeType('application/x-nacl', '', 'Native Client Executable');
|
|
||||||
const pnaclMime = makeMimeType('application/x-pnacl', '', 'Portable Native Client Executable');
|
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
makePlugin('Chrome PDF Plugin', 'Portable Document Format', 'internal-pdf-viewer', [chromePdfMime]),
|
'PDF Viewer',
|
||||||
makePlugin('Chrome PDF Viewer', '', 'mhjfbmdgcfjbbpaeojofohoefgiehjai', [pdfMime]),
|
'Chrome PDF Viewer',
|
||||||
makePlugin('Native Client', '', 'internal-nacl-plugin', [naclMime, pnaclMime]),
|
'Chromium PDF Viewer',
|
||||||
];
|
'Microsoft Edge PDF Viewer',
|
||||||
|
'WebKit built-in PDF',
|
||||||
|
].map((name) => makePlugin(name, 'Portable Document Format', 'internal-pdf-viewer', mimes));
|
||||||
|
|
||||||
const pluginArray = Object.create(PluginArray.prototype);
|
const pluginArray = Object.create(PluginArray.prototype);
|
||||||
plugins.forEach((p, i) => {
|
plugins.forEach((p, i) => {
|
||||||
pluginArray[i] = p;
|
pluginArray[i] = p;
|
||||||
pluginArray[p.name] = p;
|
pluginArray[p.name] = p;
|
||||||
});
|
});
|
||||||
Object.defineProperty(pluginArray, 'length', { get: () => plugins.length });
|
Object.defineProperty(pluginArray, 'length', { get: () => plugins.length });
|
||||||
pluginArray.item = (i) => plugins[i] || null;
|
// `i >>> 0` replicates the WebIDL unsigned-long index coercion, so
|
||||||
pluginArray.namedItem = (name) => plugins.find(p => p.name === name) || null;
|
// item(2**32) wraps to item(0) like the real native PluginArray.item.
|
||||||
pluginArray.refresh = () => {};
|
pluginArray.item = maskNative((i) => plugins[i >>> 0] || null, 'item');
|
||||||
|
pluginArray.namedItem = maskNative((name) => plugins.find(p => p.name === name) || null, 'namedItem');
|
||||||
|
pluginArray.refresh = maskNative(() => {}, 'refresh');
|
||||||
pluginArray[Symbol.iterator] = function*() { for (const p of plugins) yield p; };
|
pluginArray[Symbol.iterator] = function*() { for (const p of plugins) yield p; };
|
||||||
|
|
||||||
const mimeTypes = [chromePdfMime, pdfMime, naclMime, pnaclMime];
|
const mimeTypes = [pdfMime, textPdfMime];
|
||||||
const mimeTypeArray = Object.create(MimeTypeArray.prototype);
|
const mimeTypeArray = Object.create(MimeTypeArray.prototype);
|
||||||
mimeTypes.forEach((m, i) => {
|
mimeTypes.forEach((m, i) => {
|
||||||
mimeTypeArray[i] = m;
|
mimeTypeArray[i] = m;
|
||||||
mimeTypeArray[m.type] = m;
|
mimeTypeArray[m.type] = m;
|
||||||
});
|
});
|
||||||
Object.defineProperty(mimeTypeArray, 'length', { get: () => mimeTypes.length });
|
Object.defineProperty(mimeTypeArray, 'length', { get: () => mimeTypes.length });
|
||||||
mimeTypeArray.item = (i) => mimeTypes[i] || null;
|
mimeTypeArray.item = maskNative((i) => mimeTypes[i >>> 0] || null, 'item');
|
||||||
mimeTypeArray.namedItem = (name) => mimeTypes.find(m => m.type === name) || null;
|
mimeTypeArray.namedItem = maskNative((name) => mimeTypes.find(m => m.type === name) || null, 'namedItem');
|
||||||
mimeTypeArray[Symbol.iterator] = function*() { for (const m of mimeTypes) yield m; };
|
mimeTypeArray[Symbol.iterator] = function*() { for (const m of mimeTypes) yield m; };
|
||||||
|
|
||||||
Object.defineProperty(navigator, 'plugins', {
|
Object.defineProperty(navigator, 'plugins', {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 644 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "agent-browser connect",
|
"name": "agent-browser-stealth",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"description": "Let agent-browser drive your logged-in Chrome — install once, no token, no per-use confirmation.",
|
"description": "Let agent-browser drive your logged-in Chrome — install once, no token, no per-use confirmation.",
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6vQIyscGIPYPZdSpPwPL0+0gxUROyRgCpmvCSDoc8XUm4qm97VbKnD9Ijc1lV22lNWZtE78gaRjt6BeSfuMgnBymnhLKjN1gU6AI5QUU0mrJyeHdWKvrKQR5FmsM2A7Xr1ykE2SiiS8zNUS3Y/6O5l+Nva7wrVy6E4a2dkBVQkOsu+DV+nEZvhIyuDY5D5SPXqNwUTWTaglwj5mjvHz36xSwCWlPmrtJ+ED0AUyrb2z4GIOmvk4kqtBVrh/UD058klLo4CkYOnIybB5aV6WYuwarfPY4bF/dLggPem+ewLNTUNBuwrxj/A4nUv0LJTuRO8rR7f8WR9qnRCY0Ic5saQIDAQAB",
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6vQIyscGIPYPZdSpPwPL0+0gxUROyRgCpmvCSDoc8XUm4qm97VbKnD9Ijc1lV22lNWZtE78gaRjt6BeSfuMgnBymnhLKjN1gU6AI5QUU0mrJyeHdWKvrKQR5FmsM2A7Xr1ykE2SiiS8zNUS3Y/6O5l+Nva7wrVy6E4a2dkBVQkOsu+DV+nEZvhIyuDY5D5SPXqNwUTWTaglwj5mjvHz36xSwCWlPmrtJ+ED0AUyrb2z4GIOmvk4kqtBVrh/UD058klLo4CkYOnIybB5aV6WYuwarfPY4bF/dLggPem+ewLNTUNBuwrxj/A4nUv0LJTuRO8rR7f8WR9qnRCY0Ic5saQIDAQAB",
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
"type": "module"
|
"type": "module"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"default_title": "agent-browser connect"
|
"default_title": "agent-browser-stealth"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-browser-stealth",
|
"name": "agent-browser-stealth",
|
||||||
"version": "0.27.0-fork.28",
|
"version": "0.27.0-fork.29",
|
||||||
"description": "Browser automation CLI for AI agents \u2014 stealth fork with anti-detection",
|
"description": "Browser automation CLI for AI agents — stealth fork with anti-detection",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@11.1.3",
|
"packageManager": "pnpm@11.1.3",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||