PhantomShield

Python SDK

A pure Python automation SDK independent of the desktop client. Shares the same Chrome core, sidecar, and gost proxy chain as the Desktop version.

Installation

pip install https://pub-69fcb37602174d10b2152f09439de470.r2.dev/sdk/linege_sdk-0.3.21-py3-none-any.whl

Verify:

python -c "import linege_sdk; print(linege_sdk.__version__)"
# Output: 0.3.21

Core Flow

Login → Request Environment (envId) → Assign Proxy + Fingerprint → Launch Chrome → Sidecar Connection → Business Operations → Auto Cleanup

The SDK automatically handles: environment creation, fingerprint distribution, gost proxy mounting, sidecar startup, and process cleanup. Users only need to focus on business logic.

Quick Start

Cloud Mode — Fully Automated

from linege_sdk import LinegeClient

client = LinegeClient(username="your_username", password="your_password")

# Check quota
profile = client.get_user_profile()
print(f"Quota: {profile['used']}/{profile['total']}")

# View available countries
countries = client.list_available_countries()
print(countries)  # [{"code": "US", "proxies_available": 42, ...}, ...]

# Open browser — internally auto: request env → get envId → assign proxy + fingerprint → launch Chrome
with client.open_cloud_browser(
    start_url="https://www.wikipedia.org",
    preferred_countries=["US", "JP"],  # Prefer proxies from these countries
) as browser:
    browser.find("input#searchInput").wait().input("hello")
    browser.find("button[type='submit']").click()  # Use button click for form submission
    print(browser.find("#firstHeading").text())

When with block ends, everything is automatically cleaned up (Chrome + gost proxy + sidecar + cloud environment).

Local Mode — Specify envId + Proxy

# Create a local environment with a specified proxy, automatically mounted via gost
with client.open_local_browser(
    env_id="shop-account-01",
    proxy="socks5://user:pass@proxy-host:1080",
    start_url="https://example.com",
) as browser:
    print(browser.find("h1").text())

Notes:

  • When a Chrome core already exists locally, local mode does not require a cloud account
  • If a country is passed to fetch cloud fingerprints, or the core is not installed locally, account credentials are still required

Local Mode — Reuse Existing Environments

from linege_sdk import list_local_environments

# List all local environments (same as shown in the desktop client)
envs = list_local_environments()
for env in envs:
    print(f"{env['id']}  proxy={env['proxy']}  country={env['country']}")

# Launch using an existing environment's envId
target = envs[0]
with client.open_local_browser(
    env_id=target["id"],
    proxy=target["proxy"] or None,
    start_url="https://example.com",
) as browser:
    print(browser.find("h1").text())

Finding Elements

Three methods, returning lazy Element objects (DOM is queried only when an action is called):

MethodPurposeExample
find(selector)CSS selectorbrowser.find("input#email")
find_by_xpath(xpath)XPath selectorbrowser.find_by_xpath("//div[@id='main']")
find_by_text(text)Find by visible textbrowser.find_by_text("Login")

find_by_text — Resilient to DOM Structure Changes

# Restrict to a specific tag
browser.find_by_text("Submit", tag="button").click()

# Exact match
browser.find_by_text("Login", exact=True).click()

# Multi-language candidates — matches whichever language the page displays
browser.find_by_text(["Login", "登录", "ログイン"], tag="button").click()
ParameterDefaultDescription
textRequiredString or list of strings (multi-language candidates)
tag"*"Restrict to tag: "button" / "a" / "span" etc.
exactFalseTrue = exact match, False = contains match

Element Chaining

MethodActionReturns
.click()Human-like click (Bézier curve)Element
.input(text)Human-like input (TypeText real keyboard)Element
.press_key(key)Keyboard key pressElement
.scroll(delta_y)ScrollElement
.text()Read text contentOptional[str]
.wait(state, timeout)Wait for elementElement
.attribute(name)Read attributeOptional[str]
.is_visible()Check visibilitybool
browser.find("input#email").wait().input("user@example.com")
browser.find("input#password").click().input("secret123")
browser.find("button[type='submit']").click()  # Use button click for form submission
title = browser.find("h1").text()

Browser API

MethodActionExample
navigate(url)Legacy behavior: initiate navigation (does not wait by default)browser.navigate("https://example.com")
goto(url)Recommended: navigate and wait for page ready / URL matchbrowser.goto("https://polymarket.com", match="host")
wait_for_url(url)Wait for URL match (exact/prefix/contains/host)browser.wait_for_url("https://example.com", match="host")
scroll(delta_y)Scrollbrowser.scroll(delta_y=500)
eval_js(expr)Execute JS and return resultbrowser.eval_js("document.title")
click(selector)Direct click (configurable required)browser.click("#btn", required=False)
text(selector)Direct text read (configurable required)browser.text("#title", required=False)
input(selector, value)Direct inputbrowser.input("#email", "a@b.com")
press_key(key)Keyboard key press (regular characters)browser.press_key("a")
new_tab(url)Open a new tab and return its indexidx = browser.new_tab("https://outlook.live.com")
list_tabs()View tab summary (count/active)browser.list_tabs()
switch_tab(index)Switch to a specific tabbrowser.switch_tab(idx)
close_tab(index)Close a specific tabbrowser.close_tab(idx)

navigate() retains legacy behavior for backward compatibility (only initiates navigation, does not block and wait by default).

For client scripts, it is recommended to use:

# Navigate + wait + URL verification (host mode is ideal for anti-bot redirect scenarios)
browser.goto(
    "https://polymarket.com/event/bitcoin-above-on-march-4",
    timeout=45,
    wait_until="domcontentloaded",
    ensure_url=True,
    match="host",
)

If you prefer the legacy syntax, you can explicitly enable waiting:

browser.navigate(
    "https://polymarket.com/event/bitcoin-above-on-march-4",
    wait=True,
    ensure_url=True,
    match="host",
)

Multi-Tab API (Cross-Site Switching Within the Same Environment)

# Main tab: Polymarket
browser.goto("https://polymarket.com", match="host")

# Open new tab: Outlook
outlook_tab = browser.new_tab("https://outlook.live.com")
browser.switch_tab(outlook_tab)
browser.wait_for_url("https://outlook.live.com", match="host")

# Switch back to main tab
browser.switch_tab(0)
browser.wait_for_url("https://polymarket.com", match="host")

eval_js — Complex DOM Interactions

When find_by_text cannot precisely match (e.g., React nested text, buttons inside modals), use eval_js to directly manipulate the DOM:

# Precisely click a button inside a modal (scoped to [role=dialog])
result = browser.eval_js("""
    (function() {
        var dialog = document.querySelector('[role=dialog]');
        if (!dialog) return null;
        var btns = dialog.querySelectorAll('button');
        for (var i = 0; i < btns.length; i++) {
            if (btns[i].textContent.trim() === 'Continue' && !btns[i].disabled) {
                btns[i].click();
                return 'OK';
            }
        }
        return 'not found';
    })()
""")

Design Principles

PrincipleDescription
Use .click() for form submissionClick the submit button instead of press_key("Enter")
Keyboard for regular characters onlypress_key is only for letters/numbers/symbols
Prefer .wait()Use instead of time.sleep(), continues immediately when element is ready
Prefer goto() for navigationnavigate is only for backward compatibility; use goto in business scripts
Prefer tab API for cross-site switchingUse new_tab/switch_tab within the same environment to manage tabs
Use eval_js for complex DOMPrecisely locate buttons inside modals, React nested text, etc. via JS

Full Example — Object-Oriented Multi-Instance Business Flow

example_full.py is now an OOP orchestration script for real-world business scenarios:

  • Account preflight checks (quota / country / core)
  • Cloud business flow (auto environment + proxy + data collection)
  • Local business flow (fixed envId workspace)
  • Multi-instance parallel monitoring (3 instances with independent navigation)
  • Error guardrails (silent mode and strict mode)

Only requires username and password by default, with no dependency on email verification codes or other conditional branches.

Full source code (download from site): example_full.py

Complete source code (same as downloadable file, auto-rendered):

"""
Phantomshield SDK Full Example (Object-Oriented + Multi-Instance Real Business Flow)
=================================================

Purpose:
  - For business developers: log in with username/password, then run a complete business orchestration
  - OOP structure: one orchestration class manages preflight, business flows, multi-instance, and error guardrails
  - No email verification or conditional branches — only requires username and password

Usage:
  python examples/example_full.py --username alice --password secret
  LINEGE_USERNAME=alice LINEGE_PASSWORD=secret python examples/example_full.py
"""

from __future__ import annotations

import argparse
import json
import os
import sys
import time
import uuid
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from typing import Any, Callable, Dict, List, Optional

from linege_sdk import LinegeClient, LinegeSDKError, list_local_environments

def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")

def jlog(trace_id: str, event: str, status: str = "OK", **payload: Any) -> None:
    print(
        json.dumps(
            {
                "ts": now_iso(),
                "trace_id": trace_id,
                "event": event,
                "status": status,
                **payload,
            },
            ensure_ascii=False,
        )
    )

@dataclass
class MultiInstanceTask:
    env_id: str
    start_url: str
    next_url: str

@dataclass
class MultiInstanceResult:
    env_id: str
    start_url: str
    title_before: str
    url_before: str
    title_after: str
    url_after: str

class FullBusinessWorkflow:
    def __init__(self, username: str, password: str) -> None:
        self.trace_id = f"full-{uuid.uuid4().hex[:10]}"
        self.client = LinegeClient(username=username, password=password)
        self.report: Dict[str, Any] = {
            "trace_id": self.trace_id,
            "status": "running",
            "steps": [],
            "artifacts": {},
        }

    def run(self) -> int:
        jlog(self.trace_id, "flow.start")
        try:
            self._run_step("preflight.profile", self._preflight_profile, required=True)
            self._run_step("preflight.core", self._preflight_core, required=True)
            self._run_step("preflight.countries", self._preflight_countries, required=True)
            self._run_step("preflight.local_envs", self._preflight_local_envs, required=False)

            self._run_step("biz.cloud_research", self._biz_cloud_research, required=True)
            self._run_step("biz.local_workspace", self._biz_local_workspace, required=True)
            self._run_step("biz.multi_instance_watch", self._biz_multi_instance_watch, required=True)
            self._run_step("biz.error_guardrail", self._biz_error_guardrail, required=True)

            self.report["status"] = "ok"
            jlog(self.trace_id, "flow.success", step_count=len(self.report["steps"]))
            return 0
        except LinegeSDKError as exc:
            self.report["status"] = "failed"
            self.report["error"] = {
                "type": "LinegeSDKError",
                "code": exc.code,
                "message": exc.message,
                "hint": exc.hint,
                "detail": exc.detail,
            }
            jlog(self.trace_id, "flow.failed", status="ERROR", code=exc.code, message=exc.message)
            return 1
        except Exception as exc:
            self.report["status"] = "failed"
            self.report["error"] = {
                "type": type(exc).__name__,
                "message": str(exc),
            }
            jlog(self.trace_id, "flow.failed", status="ERROR", error_type=type(exc).__name__, message=str(exc))
            return 1
        finally:
            try:
                self.client.close_all()
            except Exception:
                pass
            jlog(self.trace_id, "flow.finish", status=self.report["status"])
            print(json.dumps(self.report, ensure_ascii=False, indent=2))

    def _run_step(self, name: str, fn: Callable[[], Any], required: bool) -> Any:
        started = time.perf_counter()
        try:
            value = fn()
            elapsed_ms = round((time.perf_counter() - started) * 1000)
            step_record = {
                "name": name,
                "required": required,
                "ok": True,
                "elapsed_ms": elapsed_ms,
                "value": value,
            }
            self.report["steps"].append(step_record)
            jlog(self.trace_id, "step.ok", step=name, elapsed_ms=elapsed_ms)
            return value
        except Exception as exc:
            elapsed_ms = round((time.perf_counter() - started) * 1000)
            step_record = {
                "name": name,
                "required": required,
                "ok": False,
                "elapsed_ms": elapsed_ms,
                "error": str(exc),
            }
            self.report["steps"].append(step_record)
            jlog(self.trace_id, "step.fail", status="ERROR", step=name, elapsed_ms=elapsed_ms, error=str(exc))
            if required:
                raise
            return None

    def _preflight_profile(self) -> Dict[str, Any]:
        profile = self.client.get_user_profile()
        return {
            "username": profile["username"],
            "quota": {
                "used": profile["used"],
                "total": profile["total"],
                "remaining": profile["remaining"],
            },
        }

    def _preflight_core(self) -> Dict[str, Any]:
        core = self.client.ensure_core_latest()
        return {
            "updated": core["updated"],
            "local_version": core["local_version"],
            "remote_version": core["remote_version"],
        }

    def _preflight_countries(self) -> Dict[str, Any]:
        countries = self.client.list_available_countries(preferred=["US", "JP"])
        top = [
            {
                "code": c["code"],
                "name": c["name"],
                "proxies_available": c["proxies_available"],
                "preferred": c["preferred"],
            }
            for c in countries[:8]
        ]
        return {
            "count": len(countries),
            "top": top,
        }

    def _preflight_local_envs(self) -> Dict[str, Any]:
        envs = list_local_environments()
        return {
            "count": len(envs),
            "sample": [
                {
                    "id": row.get("id"),
                    "proxy": bool(row.get("proxy")),
                    "country": row.get("country") or "-",
                }
                for row in envs[:8]
            ],
        }

    def _biz_cloud_research(self) -> Dict[str, Any]:
        with self.client.open_cloud_browser(
            start_url="https://www.wikipedia.org",
            preferred_countries=["US", "JP"],
        ) as browser:
            query = "Browser automation"
            browser.find("input#searchInput").wait(timeout=30).input(query)
            browser.find("button[type='submit'], input.searchButton, button.pure-button").click()

            heading = browser.find("#firstHeading").wait(timeout=30).text() or ""
            summary = (
                browser.find_by_xpath(
                    "//div[contains(@class,'mw-parser-output')]//p[string-length(normalize-space()) > 30][1]"
                ).text()
                or ""
            )
            refs_visible = browser.find_by_text(["References", "参考文献", "脚注"], tag="span").is_visible()
            browser.find("body").scroll(delta_y=500)

            result = {
                "env_id": browser.env.env_id,
                "mode": browser.env.mode,
                "country": getattr(browser.env, "country", None),
                "title": browser.eval_js("document.title"),
                "url": browser.eval_js("window.location.href"),
                "heading": heading,
                "summary_preview": summary[:160],
                "references_visible": refs_visible,
            }
            self.report["artifacts"]["cloud_env_id"] = browser.env.env_id
            return result

    def _biz_local_workspace(self) -> Dict[str, Any]:
        with self.client.open_local_browser(
            env_id="biz-workspace-main",
            start_url="https://example.com",
        ) as browser:
            homepage_h1 = browser.find("h1").wait(timeout=20).text() or ""
            homepage_link = browser.find("a").wait(timeout=10).attribute("href")
            homepage_visible = browser.find("h1").is_visible()

            browser.goto("https://www.wikipedia.org", match="host")
            input_box = browser.find("input#searchInput").wait(timeout=30)
            input_box.click()
            input_box.press_key("L")
            browser.input("input#searchInput", "inege SDK", human=True)
            browser.find("button[type='submit'], input.searchButton, button.pure-button").click()
            browser.find("#firstHeading").wait(timeout=30)

            # Multi-tab business flow: switch to Outlook in the same environment, then back to the main site
            outlook_tab = browser.new_tab("https://outlook.live.com", activate=True)
            browser.switch_tab(outlook_tab)
            outlook_hosts = [
                "https://outlook.live.com",
                "https://login.live.com",
                "https://www.microsoft.com",
            ]
            outlook_ready = browser.wait_for_url(
                outlook_hosts,
                match="host",
                timeout=30,
                required=False,
            )
            tabs_after_open = browser.list_tabs()
            browser.switch_tab(0)
            wiki_ready = browser.wait_for_url("https://wikipedia.org", match="host", timeout=30, required=False)

            browser.find("body").scroll(delta_y=600)
            result_title = browser.find("#firstHeading").text() or ""
            page_title = browser.eval_js("document.title")
            page_url = browser.eval_js("window.location.href")

            return {
                "env_id": browser.env.env_id,
                "mode": browser.env.mode,
                "homepage_h1": homepage_h1,
                "homepage_link": homepage_link,
                "homepage_visible": homepage_visible,
                "result_title": result_title,
                "page_title": page_title,
                "page_url": page_url,
                "outlook_tab_index": outlook_tab,
                "outlook_ready": outlook_ready,
                "wiki_ready_after_switch": wiki_ready,
                "tabs_after_open": tabs_after_open,
            }

    def _biz_multi_instance_watch(self) -> List[Dict[str, Any]]:
        tasks = [
            MultiInstanceTask(env_id="watch-us", start_url="https://example.com", next_url="https://www.wikipedia.org"),
            MultiInstanceTask(env_id="watch-jp", start_url="https://www.wikipedia.org", next_url="https://httpbin.org/html"),
            MultiInstanceTask(env_id="watch-eu", start_url="https://httpbin.org/html", next_url="https://example.com"),
        ]

        sessions: List[tuple[MultiInstanceTask, Any]] = []
        results: List[MultiInstanceResult] = []
        try:
            for task in tasks:
                session = self.client.open_local_browser(env_id=task.env_id, start_url=task.start_url)
                sessions.append((task, session))

            for task, session in sessions:
                session.wait("h1, h2, body", timeout=30, required=False)
                title_before = str(session.eval_js("document.title", required=False) or "")
                url_before = str(session.eval_js("window.location.href", required=False) or "")
                session.goto(task.next_url, timeout=30, ensure_url=True, match="host", required=False)

                session.wait("h1, h2, body", timeout=30, required=False)
                title_after = str(session.eval_js("document.title", required=False) or "")
                url_after = str(session.eval_js("window.location.href", required=False) or "")

                results.append(
                    MultiInstanceResult(
                        env_id=task.env_id,
                        start_url=task.start_url,
                        title_before=title_before,
                        url_before=url_before,
                        title_after=title_after,
                        url_after=url_after,
                    )
                )
        finally:
            for _, session in sessions:
                try:
                    session.close()
                except Exception:
                    pass

        return [asdict(row) for row in results]

    def _biz_error_guardrail(self) -> Dict[str, Any]:
        bad_auth: Dict[str, Any] = {}
        try:
            LinegeClient(username="wrong_user", password="bad_password").get_user_profile()
        except LinegeSDKError as exc:
            bad_auth = {
                "code": exc.code,
                "message": exc.message,
                "hint": exc.hint,
            }

        with self.client.open_local_browser(
            env_id="guardrail-silent",
            start_url="https://example.com",
        ) as browser:
            browser.find("h1").wait(timeout=10)
            silent_click = browser.click("#nonexistent-element", required=False, timeout=3)
            silent_text = browser.text("#nonexistent-element", required=False, timeout=3)

        strict_error: Dict[str, Any] = {}
        try:
            with self.client.open_local_browser(
                env_id="guardrail-strict",
                start_url="https://example.com",
            ) as browser:
                browser.find("h1").wait(timeout=10)
                browser.click("#nonexistent-element", required=True, timeout=3)
        except LinegeSDKError as exc:
            strict_error = {
                "code": exc.code,
                "message": exc.message,
                "hint": exc.hint,
            }

        return {
            "bad_auth": bad_auth,
            "silent_mode": {
                "click_result": silent_click,
                "text_result": silent_text,
            },
            "strict_mode": strict_error,
        }

def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Phantomshield SDK Object-Oriented Multi-Instance Full Business Example")
    parser.add_argument("--username", default=os.environ.get("LINEGE_USERNAME", ""))
    parser.add_argument("--password", default=os.environ.get("LINEGE_PASSWORD", ""))
    return parser.parse_args()

def main() -> int:
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    if hasattr(sys.stderr, "reconfigure"):
        sys.stderr.reconfigure(encoding="utf-8", errors="replace")

    args = parse_args()
    if not args.username or not args.password:
        print("Missing credentials: please set --username/--password or LINEGE_USERNAME/LINEGE_PASSWORD")
        return 2

    workflow = FullBusinessWorkflow(username=args.username, password=args.password)
    return workflow.run()

if __name__ == "__main__":
    raise SystemExit(main())

External Machine Verification Process (Bare Metal)

# 1) Create an isolated environment
python -m venv .venv
# Windows
.venv\Scripts\python -m pip install --upgrade pip
.venv\Scripts\python -m pip install "https://pub-69fcb37602174d10b2152f09439de470.r2.dev/sdk/linege_sdk-0.3.21-py3-none-any.whl"

# 2) Download the full example script (from site)
powershell -Command "Invoke-WebRequest -Uri https://docs.sybilslayer.com/examples/example_full.py -OutFile example_full.py"

# 3) Set credentials and run the full example (username/password mode)
set LINEGE_USERNAME=your_username
set LINEGE_PASSWORD=your_password
.venv\Scripts\python example_full.py

Expected results:

  • Outputs structured JSON logs (step.ok / step.fail)
  • Shows flow.success upon normal completion
  • Exit code is 0

LinegeClient Parameters

client = LinegeClient(
    username="...",                  # or token="..."
    password="...",
    auto_cleanup_cloud_env=True,     # Auto-delete cloud env when with block ends
    enable_logging=True,             # Structured JSON logging
    sidecar_base_port=7788,          # Auto-increments if port is occupied
    max_startup_retries=1,           # Retry count after startup failure
    desktop_compatible=False,        # True to use desktop-compatible presets
    chrome_path=None,                # Custom Chrome path
    render_mode="software",          # "software" or "gpu"
    http_cache_mode="isolated",      # "isolated" / "global" / "off"
    shared_http_cache_root=None,     # Shared HTTP cache directory
    shared_http_cache_size_bytes=None, # Shared cache size (bytes)
    restore_last_session=None,       # Restore last session (defaults follow desktop_compatible)
)
ParameterTypeDefaultDescription
usernamestrNoneUsername (mutually exclusive with token)
passwordstrNonePassword
tokenstrNoneToken (mutually exclusive with username/password)
api_urlstr"https://api.sybilslayer.com"API URL
sidecar_base_portint7788Sidecar starting port, auto-increments if occupied
timeoutfloat20.0API request timeout (seconds)
auto_cleanup_cloud_envboolTrueAuto-delete cloud env when with block ends
enable_loggingboolTrueStructured JSON logging
max_startup_retriesint1Retry count after browser startup failure
desktop_compatibleboolFalseEnable desktop-compatible presets (render_mode/cache/session, etc.)
chrome_pathstrNoneCustom Chrome executable path
render_modestr"software"Render mode: "software" or "gpu"
http_cache_modestr"isolated"HTTP cache mode: "isolated" / "global" / "off"
shared_http_cache_rootstrNoneShared HTTP cache root directory
shared_http_cache_size_bytesintNoneShared cache size limit (bytes)
restore_last_sessionboolNoneRestore last session; None follows desktop_compatible

Instance Methods

MethodDescription
get_user_profile()Returns {username, used, total, remaining}
ensure_core_latest()Ensures Chrome core is up to date (auto-downloads on first use)
list_available_countries()Queries the list of available cloud countries
open_cloud_browser(...)Cloud mode (auto request env + proxy + fingerprint)
open_local_browser(...)Local mode (specify/auto-generate envId, optional proxy)

open_cloud_browser Parameters

ParameterTypeDefaultDescription
start_urlstr"about:blank"URL to open after launch
preferred_countriesList[str]NonePreferred country code list
sidecar_portintNoneSpecify sidecar port (None for auto-assign)
env_name_prefixstr"sdk-biz"Cloud environment name prefix
attempts_per_countryint2Attempts per country
startup_retriesintNoneStartup retries (None uses constructor's max_startup_retries)
desktop_compatibleboolNoneOverride constructor's desktop_compatible
chrome_pathstrNoneOverride constructor's chrome_path
render_modestrNoneOverride constructor's render_mode
http_cache_modestrNoneOverride constructor's http_cache_mode
shared_http_cache_rootstrNoneOverride constructor's shared_http_cache_root
shared_http_cache_size_bytesintNoneOverride constructor's cache size
restore_last_sessionboolNoneOverride constructor's restore_last_session

open_local_browser Parameters

ParameterTypeDefaultDescription
start_urlstr"about:blank"URL to open after launch
env_idstrNoneEnvironment ID (None auto-generates local-{timestamp}-{hex})
proxystrNoneProxy URL (e.g., socks5://user:pass@host:port)
sidecar_portintNoneSpecify sidecar port (None for auto-assign)
env_namestr""Environment display name
countrystr""Country code (non-empty auto-fetches corresponding fingerprint from cloud)
startup_retriesintNoneStartup retries (None uses constructor's max_startup_retries)
desktop_compatibleboolNoneOverride constructor's desktop_compatible
chrome_pathstrNoneOverride constructor's chrome_path
render_modestrNoneOverride constructor's render_mode
http_cache_modestrNoneOverride constructor's http_cache_mode
shared_http_cache_rootstrNoneOverride constructor's shared_http_cache_root
shared_http_cache_size_bytesintNoneOverride constructor's cache size
restore_last_sessionboolNoneOverride constructor's restore_last_session

Standalone Functions

FunctionDescription
list_local_environments()List all local environments (same as desktop client)
create_local_environment(env_id, ...)Manually create a local environment directory

create_local_environment Parameters

ParameterTypeDefaultDescription
env_idstrRequiredUnique environment identifier
profileDictNoneFingerprint profile config dict (None auto-generates minimal default)
proxystrNoneProxy URL
namestr""Environment display name
countrystr""Country code
base_data_dirstrNoneCustom Linege data root directory

get_user_profile Return Value

{
    "username": "your_username",
    "used": 5,
    "total": 50,
    "remaining": 45,
}

list_available_countries Return Value

Returns a list of countries sorted by availability, each containing:

FieldTypeDescription
codestrCountry code (e.g., "US")
namestrCountry name
proxies_availableintNumber of available proxies
fingerprintsintNumber of available fingerprints
preferredboolWhether it's in the preferred list

GUI Compatibility Mode

When you need to replicate the login state/balance/page state from the desktop client GUI, it is recommended to:

  • Reuse the same env_id
  • Enable desktop_compatible=True
  • Explicitly pass the same chrome_path used by the GUI
  • Use start_url="about:blank" + restore_last_session=True when restoring a previous session
  • If the GUI has shared cache enabled, also pass shared_http_cache_root / shared_http_cache_size_bytes

Troubleshooting

IssueSolution
No Chrome core on first runclient.ensure_core_latest() auto-downloads
Port conflictSDK auto-increments; or set sidecar_base_port=8899
eval_js timeoutVerify Chrome has started and connected to sidecar
navigate appears not to redirectUse browser.goto(..., match="host"), or navigate(wait=True, ensure_url=True)
Need cross-site switching in the same environment (Polymarket ↔ Outlook)Use new_tab/list_tabs/switch_tab/close_tab, don't rely on manual tab clicks
Outlook opens but not on outlook.live.comMay redirect to login.live.com or microsoft.com; pass candidate host list to wait_for_url with required=False
find_by_text can't match React buttonsReact component text may be in nested <span>; use eval_js + textContent.trim() instead
Button click misaligned inside modalsScope eval_js to document.querySelector('[role=dialog]')
Elements not ready after SPA page loadUse .wait(timeout=30) or appropriate time.sleep() to wait for initial render