feat(stealth): 默认开启并收敛 chrome 指纹特征
This commit is contained in:
Generated
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agent-browser"
|
name = "agent-browser-stealth"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "agent-browser"
|
name = "agent-browser-stealth"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Fast browser automation CLI for AI agents"
|
description = "Stealth browser automation CLI for AI agents with anti-bot evasions"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -1923,10 +1923,8 @@ mod tests {
|
|||||||
cli_proxy_bypass: false,
|
cli_proxy_bypass: false,
|
||||||
cli_allow_file_access: false,
|
cli_allow_file_access: false,
|
||||||
cli_annotate: false,
|
cli_annotate: false,
|
||||||
cli_stealth: false,
|
|
||||||
annotate: false,
|
annotate: false,
|
||||||
color_scheme: None,
|
color_scheme: None,
|
||||||
stealth: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -220,7 +220,6 @@ pub fn ensure_daemon(
|
|||||||
provider: Option<&str>,
|
provider: Option<&str>,
|
||||||
device: Option<&str>,
|
device: Option<&str>,
|
||||||
session_name: Option<&str>,
|
session_name: Option<&str>,
|
||||||
stealth: bool,
|
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Result<DaemonResult, String> {
|
) -> Result<DaemonResult, String> {
|
||||||
// Check if daemon is running AND responsive
|
// Check if daemon is running AND responsive
|
||||||
@@ -366,7 +365,7 @@ pub fn ensure_daemon(
|
|||||||
cmd.env("AGENT_BROWSER_SESSION_NAME", sn);
|
cmd.env("AGENT_BROWSER_SESSION_NAME", sn);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.env("AGENT_BROWSER_STEALTH", if stealth { "1" } else { "0" });
|
cmd.env("AGENT_BROWSER_STEALTH", "1");
|
||||||
if debug {
|
if debug {
|
||||||
cmd.env("AGENT_BROWSER_DEBUG", "1");
|
cmd.env("AGENT_BROWSER_DEBUG", "1");
|
||||||
}
|
}
|
||||||
@@ -454,7 +453,7 @@ pub fn ensure_daemon(
|
|||||||
cmd.env("AGENT_BROWSER_SESSION_NAME", sn);
|
cmd.env("AGENT_BROWSER_SESSION_NAME", sn);
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.env("AGENT_BROWSER_STEALTH", if stealth { "1" } else { "0" });
|
cmd.env("AGENT_BROWSER_STEALTH", "1");
|
||||||
if debug {
|
if debug {
|
||||||
cmd.env("AGENT_BROWSER_DEBUG", "1");
|
cmd.env("AGENT_BROWSER_DEBUG", "1");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ pub struct Config {
|
|||||||
pub headers: Option<String>,
|
pub headers: Option<String>,
|
||||||
pub annotate: Option<bool>,
|
pub annotate: Option<bool>,
|
||||||
pub color_scheme: Option<String>,
|
pub color_scheme: Option<String>,
|
||||||
pub stealth: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -69,7 +68,6 @@ impl Config {
|
|||||||
headers: other.headers.or(self.headers),
|
headers: other.headers.or(self.headers),
|
||||||
annotate: other.annotate.or(self.annotate),
|
annotate: other.annotate.or(self.annotate),
|
||||||
color_scheme: other.color_scheme.or(self.color_scheme),
|
color_scheme: other.color_scheme.or(self.color_scheme),
|
||||||
stealth: other.stealth.or(self.stealth),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,7 +202,6 @@ pub struct Flags {
|
|||||||
pub session_name: Option<String>,
|
pub session_name: Option<String>,
|
||||||
pub annotate: bool,
|
pub annotate: bool,
|
||||||
pub color_scheme: Option<String>,
|
pub color_scheme: Option<String>,
|
||||||
pub stealth: bool,
|
|
||||||
|
|
||||||
// Track which launch-time options were explicitly passed via CLI
|
// Track which launch-time options were explicitly passed via CLI
|
||||||
// (as opposed to being set only via environment variables)
|
// (as opposed to being set only via environment variables)
|
||||||
@@ -218,7 +215,6 @@ pub struct Flags {
|
|||||||
pub cli_proxy_bypass: bool,
|
pub cli_proxy_bypass: bool,
|
||||||
pub cli_allow_file_access: bool,
|
pub cli_allow_file_access: bool,
|
||||||
pub cli_annotate: bool,
|
pub cli_annotate: bool,
|
||||||
pub cli_stealth: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_flags(args: &[String]) -> Flags {
|
pub fn parse_flags(args: &[String]) -> Flags {
|
||||||
@@ -283,10 +279,6 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME")
|
color_scheme: env::var("AGENT_BROWSER_COLOR_SCHEME")
|
||||||
.ok()
|
.ok()
|
||||||
.or(config.color_scheme),
|
.or(config.color_scheme),
|
||||||
stealth: match env::var("AGENT_BROWSER_STEALTH") {
|
|
||||||
Ok(val) => !matches!(val.to_lowercase().as_str(), "0" | "false" | "no" | ""),
|
|
||||||
Err(_) => config.stealth.unwrap_or(true),
|
|
||||||
},
|
|
||||||
cli_executable_path: false,
|
cli_executable_path: false,
|
||||||
cli_extensions: false,
|
cli_extensions: false,
|
||||||
cli_profile: false,
|
cli_profile: false,
|
||||||
@@ -297,7 +289,6 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
cli_proxy_bypass: false,
|
cli_proxy_bypass: false,
|
||||||
cli_allow_file_access: false,
|
cli_allow_file_access: false,
|
||||||
cli_annotate: false,
|
cli_annotate: false,
|
||||||
cli_stealth: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@@ -453,14 +444,6 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"--stealth" => {
|
|
||||||
let (val, consumed) = parse_bool_arg(args, i);
|
|
||||||
flags.stealth = val;
|
|
||||||
flags.cli_stealth = true;
|
|
||||||
if consumed {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"--color-scheme" => {
|
"--color-scheme" => {
|
||||||
if let Some(s) = args.get(i + 1) {
|
if let Some(s) = args.get(i + 1) {
|
||||||
flags.color_scheme = Some(s.clone());
|
flags.color_scheme = Some(s.clone());
|
||||||
@@ -492,7 +475,6 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
|||||||
"--allow-file-access",
|
"--allow-file-access",
|
||||||
"--auto-connect",
|
"--auto-connect",
|
||||||
"--annotate",
|
"--annotate",
|
||||||
"--stealth",
|
|
||||||
];
|
];
|
||||||
// Global flags that always take a value (need to skip the next arg too)
|
// Global flags that always take a value (need to skip the next arg too)
|
||||||
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
||||||
|
|||||||
+3
-22
@@ -52,14 +52,6 @@ fn parse_proxy(proxy_str: &str) -> serde_json::Value {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_stealth_debug(resp: &connection::Response) {
|
|
||||||
if let Some(data) = &resp.data {
|
|
||||||
if let Some(stealth) = data.get("stealth") {
|
|
||||||
eprintln!("[DEBUG] stealth: {}", stealth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_session(args: &[String], session: &str, json_mode: bool) {
|
fn run_session(args: &[String], session: &str, json_mode: bool) {
|
||||||
let subcommand = args.get(1).map(|s| s.as_str());
|
let subcommand = args.get(1).map(|s| s.as_str());
|
||||||
|
|
||||||
@@ -234,7 +226,6 @@ fn main() {
|
|||||||
flags.provider.as_deref(),
|
flags.provider.as_deref(),
|
||||||
flags.device.as_deref(),
|
flags.device.as_deref(),
|
||||||
flags.session_name.as_deref(),
|
flags.session_name.as_deref(),
|
||||||
flags.stealth,
|
|
||||||
flags.debug,
|
flags.debug,
|
||||||
) {
|
) {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
@@ -291,7 +282,6 @@ fn main() {
|
|||||||
},
|
},
|
||||||
flags.ignore_https_errors.then_some("--ignore-https-errors"),
|
flags.ignore_https_errors.then_some("--ignore-https-errors"),
|
||||||
flags.cli_allow_file_access.then_some("--allow-file-access"),
|
flags.cli_allow_file_access.then_some("--allow-file-access"),
|
||||||
flags.cli_stealth.then_some("--stealth"),
|
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -472,9 +462,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if flags.debug {
|
|
||||||
print_stealth_debug(&resp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if flags.json {
|
if flags.json {
|
||||||
@@ -512,9 +500,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if flags.debug {
|
|
||||||
print_stealth_debug(&resp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if flags.json {
|
if flags.json {
|
||||||
@@ -537,7 +523,6 @@ fn main() {
|
|||||||
|| flags.user_agent.is_some()
|
|| flags.user_agent.is_some()
|
||||||
|| flags.ignore_https_errors
|
|| flags.ignore_https_errors
|
||||||
|| flags.allow_file_access
|
|| flags.allow_file_access
|
||||||
|| flags.cli_stealth
|
|
||||||
|| flags.debug
|
|| flags.debug
|
||||||
|| flags.color_scheme.is_some())
|
|| flags.color_scheme.is_some())
|
||||||
&& flags.cdp.is_none()
|
&& flags.cdp.is_none()
|
||||||
@@ -601,8 +586,6 @@ fn main() {
|
|||||||
launch_cmd["allowFileAccess"] = json!(true);
|
launch_cmd["allowFileAccess"] = json!(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
launch_cmd["stealth"] = json!(flags.stealth);
|
|
||||||
|
|
||||||
if let Some(ref cs) = flags.color_scheme {
|
if let Some(ref cs) = flags.color_scheme {
|
||||||
launch_cmd["colorScheme"] = json!(cs);
|
launch_cmd["colorScheme"] = json!(cs);
|
||||||
}
|
}
|
||||||
@@ -621,9 +604,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if flags.debug {
|
|
||||||
print_stealth_debug(&resp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if flags.json {
|
if flags.json {
|
||||||
|
|||||||
+1
-2
@@ -2114,7 +2114,6 @@ Options:
|
|||||||
e.g., --proxy-bypass "localhost,*.internal.com"
|
e.g., --proxy-bypass "localhost,*.internal.com"
|
||||||
--ignore-https-errors Ignore HTTPS certificate errors
|
--ignore-https-errors Ignore HTTPS certificate errors
|
||||||
--allow-file-access Allow file:// URLs to access local files (Chromium only)
|
--allow-file-access Allow file:// URLs to access local files (Chromium only)
|
||||||
--stealth Stealth mode (default: on): local=launch args+init scripts, CDP/provider=init scripts
|
|
||||||
-p, --provider <name> Browser provider: ios, browserbase, kernel, browseruse
|
-p, --provider <name> Browser provider: ios, browserbase, kernel, browseruse
|
||||||
--device <name> iOS device name (e.g., "iPhone 15 Pro")
|
--device <name> iOS device name (e.g., "iPhone 15 Pro")
|
||||||
--json JSON output
|
--json JSON output
|
||||||
@@ -2126,7 +2125,7 @@ Options:
|
|||||||
--color-scheme <scheme> Color scheme: dark, light, no-preference (or AGENT_BROWSER_COLOR_SCHEME)
|
--color-scheme <scheme> Color scheme: dark, light, no-preference (or AGENT_BROWSER_COLOR_SCHEME)
|
||||||
--session-name <name> Auto-save/restore session state (cookies, localStorage)
|
--session-name <name> Auto-save/restore session state (cookies, localStorage)
|
||||||
--config <path> Use a custom config file (or AGENT_BROWSER_CONFIG env)
|
--config <path> Use a custom config file (or AGENT_BROWSER_CONFIG env)
|
||||||
--debug Debug output (includes stealth connection type + capabilities)
|
--debug Debug output
|
||||||
--version, -V Show version
|
--version, -V Show version
|
||||||
|
|
||||||
Configuration:
|
Configuration:
|
||||||
|
|||||||
+5
-18
@@ -152,7 +152,7 @@ export class BrowserManager {
|
|||||||
private lastSnapshot: string = '';
|
private lastSnapshot: string = '';
|
||||||
private scopedHeaderRoutes: Map<string, (route: Route) => Promise<void>> = new Map();
|
private scopedHeaderRoutes: Map<string, (route: Route) => Promise<void>> = new Map();
|
||||||
private colorScheme: 'light' | 'dark' | 'no-preference' | null = null;
|
private colorScheme: 'light' | 'dark' | 'no-preference' | null = null;
|
||||||
private stealthEnabled: boolean = false;
|
private stealthEnabled: boolean = true;
|
||||||
private stealthConnectionKind: StealthConnectionKind = 'local';
|
private stealthConnectionKind: StealthConnectionKind = 'local';
|
||||||
private contextLocale: string | undefined = undefined;
|
private contextLocale: string | undefined = undefined;
|
||||||
private contextTimezoneId: string | undefined = undefined;
|
private contextTimezoneId: string | undefined = undefined;
|
||||||
@@ -172,17 +172,6 @@ export class BrowserManager {
|
|||||||
* Local Chromium gets args + init scripts; CDP/providers get init scripts only.
|
* Local Chromium gets args + init scripts; CDP/providers get init scripts only.
|
||||||
*/
|
*/
|
||||||
private getStealthPolicy(browserType: BrowserType = 'chromium'): StealthPolicy {
|
private getStealthPolicy(browserType: BrowserType = 'chromium'): StealthPolicy {
|
||||||
if (!this.stealthEnabled) {
|
|
||||||
return {
|
|
||||||
enabled: false,
|
|
||||||
connectionKind: this.stealthConnectionKind,
|
|
||||||
applyChromiumArgs: false,
|
|
||||||
applyInitScripts: false,
|
|
||||||
providerManaged: false,
|
|
||||||
capabilities: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyChromiumArgs = this.stealthConnectionKind === 'local' && browserType === 'chromium';
|
const applyChromiumArgs = this.stealthConnectionKind === 'local' && browserType === 'chromium';
|
||||||
const applyInitScripts = true;
|
const applyInitScripts = true;
|
||||||
const providerManaged = this.stealthConnectionKind === 'provider-kernel';
|
const providerManaged = this.stealthConnectionKind === 'provider-kernel';
|
||||||
@@ -1414,11 +1403,9 @@ export class BrowserManager {
|
|||||||
if (options.colorScheme) {
|
if (options.colorScheme) {
|
||||||
this.colorScheme = options.colorScheme;
|
this.colorScheme = options.colorScheme;
|
||||||
}
|
}
|
||||||
this.stealthEnabled = options.stealth ?? false;
|
this.stealthEnabled = true;
|
||||||
this.contextLocale = this.stealthEnabled
|
this.contextLocale = this.resolveStealthLocale(options.headers);
|
||||||
? this.resolveStealthLocale(options.headers)
|
this.contextTimezoneId = this.resolveStealthTimezoneId();
|
||||||
: undefined;
|
|
||||||
this.contextTimezoneId = this.stealthEnabled ? this.resolveStealthTimezoneId() : undefined;
|
|
||||||
this.contextHeaders = undefined;
|
this.contextHeaders = undefined;
|
||||||
this.contextUserAgent = options.userAgent;
|
this.contextUserAgent = options.userAgent;
|
||||||
// -p flag takes precedence over AGENT_BROWSER_PROVIDER.
|
// -p flag takes precedence over AGENT_BROWSER_PROVIDER.
|
||||||
@@ -2749,7 +2736,7 @@ export class BrowserManager {
|
|||||||
this.isPersistentContext = false;
|
this.isPersistentContext = false;
|
||||||
this.activePageIndex = 0;
|
this.activePageIndex = 0;
|
||||||
this.colorScheme = null;
|
this.colorScheme = null;
|
||||||
this.stealthEnabled = false;
|
this.stealthEnabled = true;
|
||||||
this.stealthConnectionKind = 'local';
|
this.stealthConnectionKind = 'local';
|
||||||
this.contextLocale = undefined;
|
this.contextLocale = undefined;
|
||||||
this.contextTimezoneId = undefined;
|
this.contextTimezoneId = undefined;
|
||||||
|
|||||||
+2
-2
@@ -450,7 +450,7 @@ export async function startDaemon(options?: {
|
|||||||
|
|
||||||
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
||||||
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
||||||
const stealth = process.env.AGENT_BROWSER_STEALTH !== '0';
|
// Stealth is always enabled in agent-browser-stealth
|
||||||
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
const colorSchemeEnv = process.env.AGENT_BROWSER_COLOR_SCHEME;
|
||||||
const colorScheme =
|
const colorScheme =
|
||||||
colorSchemeEnv === 'dark' ||
|
colorSchemeEnv === 'dark' ||
|
||||||
@@ -471,7 +471,7 @@ export async function startDaemon(options?: {
|
|||||||
proxy,
|
proxy,
|
||||||
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
||||||
allowFileAccess: allowFileAccess,
|
allowFileAccess: allowFileAccess,
|
||||||
stealth,
|
|
||||||
colorScheme,
|
colorScheme,
|
||||||
autoStateFilePath: getSessionAutoStatePath(),
|
autoStateFilePath: getSessionAutoStatePath(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ const launchSchema = baseCommandSchema.extend({
|
|||||||
provider: z.string().optional(),
|
provider: z.string().optional(),
|
||||||
ignoreHTTPSErrors: z.boolean().optional(),
|
ignoreHTTPSErrors: z.boolean().optional(),
|
||||||
allowFileAccess: z.boolean().optional(),
|
allowFileAccess: z.boolean().optional(),
|
||||||
// Stealth toggle is part of launch semantics for local/CDP/provider modes.
|
|
||||||
stealth: z.boolean().optional(),
|
|
||||||
colorScheme: z.enum(['light', 'dark', 'no-preference']).optional(),
|
colorScheme: z.enum(['light', 'dark', 'no-preference']).optional(),
|
||||||
profile: z.string().optional(),
|
profile: z.string().optional(),
|
||||||
storageState: z.string().optional(),
|
storageState: z.string().optional(),
|
||||||
|
|||||||
+13
-3
@@ -244,8 +244,18 @@ function patchNavigatorWebdriver(): string {
|
|||||||
*/
|
*/
|
||||||
function patchChromeRuntime(): string {
|
function patchChromeRuntime(): string {
|
||||||
return `(function(){
|
return `(function(){
|
||||||
if (!window.chrome) { window.chrome = {}; }
|
const chromeObject = ('chrome' in window && window.chrome) ? window.chrome : {};
|
||||||
if (!window.chrome.runtime) {
|
if (!('chrome' in window)) {
|
||||||
|
try {
|
||||||
|
Object.defineProperty(Window.prototype, 'chrome', {
|
||||||
|
get: () => chromeObject,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
try { Object.defineProperty(window, 'chrome', { value: chromeObject, configurable: true }); } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!chromeObject.runtime) {
|
||||||
const makeEvent = () => ({
|
const makeEvent = () => ({
|
||||||
addListener: () => {},
|
addListener: () => {},
|
||||||
removeListener: () => {},
|
removeListener: () => {},
|
||||||
@@ -268,7 +278,7 @@ function patchChromeRuntime(): string {
|
|||||||
onConnect: makeEvent(),
|
onConnect: makeEvent(),
|
||||||
onMessage: makeEvent(),
|
onMessage: makeEvent(),
|
||||||
};
|
};
|
||||||
Object.defineProperty(window.chrome, 'runtime', {
|
Object.defineProperty(chromeObject, 'runtime', {
|
||||||
value: runtime,
|
value: runtime,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export interface LaunchCommand extends BaseCommand {
|
|||||||
ignoreHTTPSErrors?: boolean;
|
ignoreHTTPSErrors?: boolean;
|
||||||
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
||||||
colorScheme?: 'light' | 'dark' | 'no-preference'; // Persistent color scheme override
|
colorScheme?: 'light' | 'dark' | 'no-preference'; // Persistent color scheme override
|
||||||
stealth?: boolean; // Enable stealth mode to avoid automation detection
|
|
||||||
// Auto-load state file for session persistence
|
// Auto-load state file for session persistence
|
||||||
autoStateFilePath?: string;
|
autoStateFilePath?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user