Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ jobs:
components: rustfmt, clippy
- run: cargo fmt --all -- --check
- run: cargo test --all-targets --locked
- name: Verify tests ignore inherited proxy configuration
run: >-
env -u NO_PROXY -u no_proxy
HTTP_PROXY=http://127.0.0.1:1 HTTPS_PROXY=http://127.0.0.1:1 ALL_PROXY=http://127.0.0.1:1
http_proxy=http://127.0.0.1:1 https_proxy=http://127.0.0.1:1 all_proxy=http://127.0.0.1:1
cargo test --all-targets --locked --offline
- run: cargo clippy --all-targets --locked -- -D warnings
- name: Verify downstream TLS backend compatibility
run: ./tests/test_tls_backends.py
- name: Verify release target contract
run: |
grep -q 'x86_64-unknown-linux-musl' .github/workflows/release.yml
Expand Down
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lexmount-browser"
version = "1.2.0"
version = "1.2.1"
edition = "2024"
license = "MIT"
description = "Native Rust SDK and CLI for Lexmount cloud browsers"
Expand All @@ -18,6 +18,8 @@ path = "src/main.rs"
base64 = "0.22"
clap = { version = "4.5", features = ["derive"] }
dirs = "6"
httparse = "1"
hyper-util = { version = "0.1.20", features = ["client-proxy"] }
open = "5"
rand = "0.9"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls-native-roots"] }
Expand Down
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,48 @@ are never printed.
All commands emit one JSON document. Run `browser-cli --help` for the complete
surface.

## Cloud runtime proxies

Version 1.2.1 routes CDP WebSocket connections through the environment's HTTP
CONNECT proxy. `wss://` uses `HTTPS_PROXY` and `ws://` uses `HTTP_PROXY`, with
`ALL_PROXY` as the fallback; lowercase variables and `NO_PROXY` are handled by
the same proxy matcher used by the HTTP client. Target DNS is resolved by the
proxy. Proxy Basic authentication stays on CONNECT and is not forwarded to CDP.
TLS certificate and hostname checks remain enabled. A rejected proxy request
never falls back to a direct connection.

This transport currently accepts `http://` proxies only; HTTPS-to-proxy and
SOCKS proxies return an explicit unsupported configuration error. Direct and
proxied connections share one 15-second network connection budget, covering
DNS, TCP, CONNECT and TLS/WebSocket handshake across all redirects. CONNECT
headers are limited to 16 KiB. Each blocking network operation uses the remaining
budget; a slow peer cannot restart it by sending another byte.

OS DNS resolution preserves hosts/VPN configuration. Two process-wide workers
and four queue slots bound background work. The caller stops waiting at its
deadline; an in-flight OS lookup cannot be cancelled, and its late result cannot
open a connection. Expired queued lookups are skipped. If the pool is saturated,
new hostname lookups fail with `DNS resolver busy; retry later` until workers
recover. Numeric addresses bypass DNS.

Connection timeouts exit the CLI with status 1 and a JSON error on stderr, e.g.
`{"ok":false,"error":"timeout","message":"request timed out: CDP connection (stage: proxy_dns, budget: 15s)"}`.
Stage names distinguish `proxy_dns`/`target_dns`, `proxy_tcp`/`target_tcp`,
`proxy_connect`, and `websocket_handshake`/`tls_websocket_handshake` without exposing
URLs or credentials. A connection timeout occurs before any CDP command is sent.
The budget ends at the WebSocket upgrade: REST session requests, CDP target
attachment, and later browser actions retain their existing timeout behavior.
It is not a deadline for an entire CLI command or Agent turn, nor does it trigger
automatic action retries. These changes require a new CLI release; published
1.1.15 and 1.2.0 binaries do not acquire them by updating Skill instructions.

## Select a page in a multi-tab session

Explicit page selection is introduced in version 1.2.0. Check that the installed
binary's `browser-cli action --help` lists `--target-id`; the published 1.1.15
binary does not have it. The package version and both bootstrap scripts target
1.2.0 together. Merging or building this source does not publish release assets:
bootstrap can install 1.2.0 only after its binaries and checksums are published
1.2.1 together. Merging or building this source does not publish release assets:
bootstrap can install 1.2.1 only after its binaries and checksums are published
to COS. Until then, use a source build for local verification.

Every `action` command accepts an optional `--target-id`. Obtain the page's CDP
Expand Down
2 changes: 1 addition & 1 deletion skills/lexmount-browser/scripts/bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function Invoke-Tls12Download {
}
}

$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.2.0" }
$version = if ($env:LEXMOUNT_BROWSER_CLI_VERSION) { $env:LEXMOUNT_BROWSER_CLI_VERSION } else { "1.2.1" }
$downloadBaseUrl = if ($env:LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL) { $env:LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL.TrimEnd('/') } else { "https://cli-bin-1377899528.cos.ap-nanjing.myqcloud.com/releases/browser-cli" }
$architecture = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
if ($architecture -ne "AMD64") { throw "Only Windows x64 is supported" }
Expand Down
2 changes: 1 addition & 1 deletion skills/lexmount-browser/scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
set -eu

version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.2.0}"
version="${LEXMOUNT_BROWSER_CLI_VERSION:-1.2.1}"
download_base_url="${LEXMOUNT_BROWSER_CLI_DOWNLOAD_BASE_URL:-https://cli-bin-1377899528.cos.ap-nanjing.myqcloud.com/releases/browser-cli}"
repo="${download_base_url%/}/v${version}"
case "$(uname -s)-$(uname -m)" in
Expand Down
9 changes: 5 additions & 4 deletions src/cdp.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use std::{
collections::VecDeque,
fs,
net::TcpStream,
path::Path,
time::{Duration, Instant},
};

use base64::{Engine, engine::general_purpose::STANDARD};
use serde_json::{Value, json};
use tungstenite::{Message, WebSocket, stream::MaybeTlsStream};
use tungstenite::Message;

use crate::{Error, Result};

mod proxy;

pub struct Cdp {
socket: WebSocket<MaybeTlsStream<TcpStream>>,
socket: proxy::Socket,
next_id: u64,
target_session_id: String,
events: VecDeque<Value>,
Expand Down Expand Up @@ -51,7 +52,7 @@ impl Cdp {
}

fn connect_with_target(url: &str, requested_target: Option<&str>) -> Result<Self> {
let (socket, _) = tungstenite::connect(url)?;
let socket = proxy::connect(url)?;
let mut client = Self {
socket,
next_id: 1,
Expand Down
Loading
Loading