browser-automation-cli 0.7.2__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/Cargo.lock +1 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/Cargo.toml +1 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/PKG-INFO +3 -3
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/README.md +2 -2
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/SKILL.md +2 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/install.sh +1 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/pyproject.toml +1 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/actions.rs +19 -2
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/cdp.rs +5 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/main.rs +15 -3
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/server.rs +17 -1
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/.gitignore +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/chrome.rs +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/daemon_main.rs +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/install.rs +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/js.rs +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/lib.rs +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/session.rs +0 -0
- {browser_automation_cli-0.7.2 → browser_automation_cli-0.8.2}/src/update.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: browser-automation-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Operating System :: MacOS
|
|
@@ -141,7 +141,7 @@ All print JSON (snapshot prints text). Add `-s` / `--snapshot` to any action to
|
|
|
141
141
|
| :-- | :-- |
|
|
142
142
|
| `navigate <url> [--wait load\|domcontentloaded\|networkidle]` | Returns as soon as the page is usable; never fails on a slow `networkidle` |
|
|
143
143
|
| `snapshot [scope] [--all] [--max N] [--json]` | Visible interactive elements + headings. `--all` adds text blocks, `--json` gives boxes and unique selectors |
|
|
144
|
-
| `click <target> [--double]` | |
|
|
144
|
+
| `click <target> [--double]` | Also `click --at X,Y` to click raw viewport pixels (canvas / vision — no DOM target; screenshot first, pixels map 1:1) |
|
|
145
145
|
| `type <target> <text> [--sequential] [--submit]` | `fill()` by default; `--sequential` sends key events (autocomplete); `--submit` presses Enter |
|
|
146
146
|
| `press <key> [target]` | `Enter`, `Tab`, `Control+a`, … |
|
|
147
147
|
| `hover <target>` | |
|
|
@@ -155,7 +155,7 @@ All print JSON (snapshot prints text). Add `-s` / `--snapshot` to any action to
|
|
|
155
155
|
| `back` / `forward` | |
|
|
156
156
|
| `batch` | JSON lines on stdin, run in one round-trip, stop at first failure |
|
|
157
157
|
|
|
158
|
-
**Targets:** `@e12` (ref from snapshot — preferred) · CSS selector · `text=Create` · `role=button[name=Create]` · `label=Email` · `placeholder=Search` · or flags `--text / --role [--name] / --label / --placeholder`. Ambiguous CSS selectors are refused (strict mode) instead of clicking the first match.
|
|
158
|
+
**Targets:** `@e12` (ref from snapshot — preferred) · CSS selector · `text=Create` · `role=button[name=Create]` · `label=Email` · `placeholder=Search` · or flags `--text / --role [--name] / --label / --placeholder`. Ambiguous CSS selectors are refused (strict mode) instead of clicking the first match. For canvas / vision cases with no DOM target, `click --at X,Y` clicks raw viewport pixels (take a `screenshot` first; its pixels map 1:1 to click coordinates).
|
|
159
159
|
|
|
160
160
|
***
|
|
161
161
|
|
|
@@ -126,7 +126,7 @@ All print JSON (snapshot prints text). Add `-s` / `--snapshot` to any action to
|
|
|
126
126
|
| :-- | :-- |
|
|
127
127
|
| `navigate <url> [--wait load\|domcontentloaded\|networkidle]` | Returns as soon as the page is usable; never fails on a slow `networkidle` |
|
|
128
128
|
| `snapshot [scope] [--all] [--max N] [--json]` | Visible interactive elements + headings. `--all` adds text blocks, `--json` gives boxes and unique selectors |
|
|
129
|
-
| `click <target> [--double]` | |
|
|
129
|
+
| `click <target> [--double]` | Also `click --at X,Y` to click raw viewport pixels (canvas / vision — no DOM target; screenshot first, pixels map 1:1) |
|
|
130
130
|
| `type <target> <text> [--sequential] [--submit]` | `fill()` by default; `--sequential` sends key events (autocomplete); `--submit` presses Enter |
|
|
131
131
|
| `press <key> [target]` | `Enter`, `Tab`, `Control+a`, … |
|
|
132
132
|
| `hover <target>` | |
|
|
@@ -140,7 +140,7 @@ All print JSON (snapshot prints text). Add `-s` / `--snapshot` to any action to
|
|
|
140
140
|
| `back` / `forward` | |
|
|
141
141
|
| `batch` | JSON lines on stdin, run in one round-trip, stop at first failure |
|
|
142
142
|
|
|
143
|
-
**Targets:** `@e12` (ref from snapshot — preferred) · CSS selector · `text=Create` · `role=button[name=Create]` · `label=Email` · `placeholder=Search` · or flags `--text / --role [--name] / --label / --placeholder`. Ambiguous CSS selectors are refused (strict mode) instead of clicking the first match.
|
|
143
|
+
**Targets:** `@e12` (ref from snapshot — preferred) · CSS selector · `text=Create` · `role=button[name=Create]` · `label=Email` · `placeholder=Search` · or flags `--text / --role [--name] / --label / --placeholder`. Ambiguous CSS selectors are refused (strict mode) instead of clicking the first match. For canvas / vision cases with no DOM target, `click --at X,Y` clicks raw viewport pixels (take a `screenshot` first; its pixels map 1:1 to click coordinates).
|
|
144
144
|
|
|
145
145
|
***
|
|
146
146
|
|
|
@@ -73,6 +73,7 @@ browser shutdown
|
|
|
73
73
|
browser <id> navigate <url> [-s] # -s on any action: append a fresh snapshot
|
|
74
74
|
browser <id> snapshot [scope] [--all] [--max N] [--json]
|
|
75
75
|
browser <id> click <target> [--double] [-s]
|
|
76
|
+
browser <id> click --at X,Y [--double] # click raw viewport pixels (canvas / vision — no DOM target)
|
|
76
77
|
browser <id> type <target> <text> [--sequential] [--submit] [-s] # alias: fill
|
|
77
78
|
browser <id> press <key> [target] # Enter, Tab, Escape, Control+a
|
|
78
79
|
browser <id> hover <target>
|
|
@@ -87,7 +88,7 @@ browser <id> back | forward
|
|
|
87
88
|
browser <id> batch # JSON lines on stdin, one round-trip
|
|
88
89
|
```
|
|
89
90
|
|
|
90
|
-
**Targets:** `@e12` (ref from snapshot — preferred) · `--text "Create"` · `--role button --name Create` · `--label "Email"` · `--placeholder Search` · CSS selector. Ambiguous CSS selectors are refused; use a ref or text.
|
|
91
|
+
**Targets:** `@e12` (ref from snapshot — preferred) · `--text "Create"` · `--role button --name Create` · `--label "Email"` · `--placeholder Search` · CSS selector. Ambiguous CSS selectors are refused; use a ref or text. For canvas / vision cases with no DOM target, `click --at X,Y` clicks raw viewport pixels (take a `screenshot` first; its pixels map 1:1 to click coordinates).
|
|
91
92
|
|
|
92
93
|
## Snapshot Format
|
|
93
94
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# Install the Rust browser/browser-daemon binaries from the GitHub release into ~/.local/bin.
|
|
3
3
|
set -e
|
|
4
|
-
V="${BROWSER_CLI_VERSION:-0.
|
|
4
|
+
V="${BROWSER_CLI_VERSION:-0.8.2}"; OS=$(uname -s | tr A-Z a-z); ARCH=$(uname -m)
|
|
5
5
|
URL="https://github.com/jshan9078/browser-automation-cli/releases/download/v$V/browser-cli-$V-$OS-$ARCH.tar.gz"
|
|
6
6
|
DEST="${BROWSER_CLI_BIN:-$HOME/.local/bin}"; mkdir -p "$DEST"
|
|
7
7
|
echo "Downloading $URL"; curl -fsSL "$URL" | tar -C "$DEST" -xzf -
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "browser-automation-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.2"
|
|
8
8
|
description = "Browser automation daemon + CLI for coding agents. Persistent sessions, no MCP, no extensions."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -61,6 +61,17 @@ impl Page {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/// Recover a wedged session. A stuck renderer main thread makes Runtime.evaluate (snapshot/eval/
|
|
65
|
+
/// click) hang until the CDP timeout, and it never comes back on its own. Page.reload is handled by
|
|
66
|
+
/// the browser process (not the stuck renderer), so it replaces the renderer with a fresh one and
|
|
67
|
+
/// un-wedges the session. Bounded so recovery itself can't hang. Loses page state (the session was
|
|
68
|
+
/// already unusable), but restores a working session for the caller to retry.
|
|
69
|
+
pub async fn recover(&self) -> Result<(), String> {
|
|
70
|
+
self.send("Page.reload", json!({"ignoreCache": false})).await?;
|
|
71
|
+
let _ = self.wait_ready("interactive", 15000).await;
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
async fn after(&self, mut result: Map<String, Value>, snap: bool, settle: bool) -> Value {
|
|
65
76
|
if settle || snap { self.settle().await; }
|
|
66
77
|
result.insert("url".into(), json!(self.url().await));
|
|
@@ -189,8 +200,14 @@ pub async fn snapshot_p(page: &Page, p: &Map<String, Value>) -> Value {
|
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
pub async fn click(page: &Page, p: &Map<String, Value>) -> Value {
|
|
192
|
-
|
|
193
|
-
let (x, y) = (
|
|
203
|
+
// Raw viewport pixel click (`click --at X,Y`) when x/y are given; else resolve a DOM target.
|
|
204
|
+
let (x, y) = match (p.get("x").and_then(|v| v.as_f64()), p.get("y").and_then(|v| v.as_f64())) {
|
|
205
|
+
(Some(x), Some(y)) => (x, y),
|
|
206
|
+
_ => {
|
|
207
|
+
let r = match page.resolve(&target_of(p), ACTION_TIMEOUT_MS).await { Ok(r) => r, Err(e) => return fail(e) };
|
|
208
|
+
(r["x"].as_f64().unwrap_or(0.0), r["y"].as_f64().unwrap_or(0.0))
|
|
209
|
+
}
|
|
210
|
+
};
|
|
194
211
|
if let Err(e) = page.click_at(x, y, if flag(p, "double") { 2 } else { 1 }).await { return fail(e); }
|
|
195
212
|
page.after(ok(), flag(p, "snap"), true).await
|
|
196
213
|
}
|
|
@@ -63,7 +63,11 @@ impl Cdp {
|
|
|
63
63
|
let mut msg = json!({"id": id, "method": method, "params": params});
|
|
64
64
|
if let Some(sid) = session { msg["sessionId"] = json!(sid); }
|
|
65
65
|
self.tx.send(msg.to_string()).map_err(|_| "cdp send: connection closed".to_string())?;
|
|
66
|
-
|
|
66
|
+
// Per-command timeout. Configurable via BROWSER_CDP_TIMEOUT_MS so a wedged renderer fails fast
|
|
67
|
+
// (default 60s, unchanged). On timeout the pending entry is dropped and the caller gets an error
|
|
68
|
+
// it can act on (server.rs then attempts a reload-based recovery).
|
|
69
|
+
let timeout_ms: u64 = std::env::var("BROWSER_CDP_TIMEOUT_MS").ok().and_then(|v| v.parse().ok()).filter(|&v| v > 0).unwrap_or(60_000);
|
|
70
|
+
match tokio::time::timeout(std::time::Duration::from_millis(timeout_ms), r).await {
|
|
67
71
|
Ok(Ok(v)) => v,
|
|
68
72
|
Ok(Err(_)) => Err("cdp: response channel dropped".into()),
|
|
69
73
|
Err(_) => { self.pending.lock().unwrap().remove(&id); Err(format!("cdp: {method} timed out")) }
|
|
@@ -42,7 +42,7 @@ Page commands (all print JSON; add -s/--snapshot to include a fresh snapshot in
|
|
|
42
42
|
browser <id> snapshot [scope-selector] [--all] [--max N] [--json]
|
|
43
43
|
Interactive elements as "@e12 button "Create"" lines.
|
|
44
44
|
--all adds text blocks; --json gives structured output
|
|
45
|
-
browser <id> click <target> [--double]
|
|
45
|
+
browser <id> click <target> [--double] (or `--at X,Y` to click raw viewport pixels — canvas/vision)
|
|
46
46
|
browser <id> type <target> <text> [--sequential] [--submit] (alias: fill)
|
|
47
47
|
browser <id> press <key> [target] e.g. Enter, Tab, Control+a
|
|
48
48
|
browser <id> hover <target>
|
|
@@ -128,7 +128,7 @@ fn parse_flags(args: &[String], bools: &[&str], valued: &[&str]) -> (Vec<String>
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const BOOLS: &[&str] = &["-s", "--snapshot", "--all", "--json", "--double", "--sequential", "--submit", "--gone", "-f", "--full-page", "--clear", "--table"];
|
|
131
|
-
const VALUED: &[&str] = &["--text", "--role", "--name", "--label", "--placeholder", "--wait", "--max", "--timeout", "-o", "--output", "-q", "--quality", "--selector", "--format"];
|
|
131
|
+
const VALUED: &[&str] = &["--text", "--role", "--name", "--label", "--placeholder", "--wait", "--max", "--timeout", "-o", "--output", "-q", "--quality", "--selector", "--format", "--at"];
|
|
132
132
|
|
|
133
133
|
fn target_params(f: &HashMap<String, Value>) -> Map<String, Value> {
|
|
134
134
|
let mut p = Map::new();
|
|
@@ -166,7 +166,19 @@ fn build(sid: &str, action: &str, rest: &[String]) -> Option<Value> {
|
|
|
166
166
|
req("snapshot", p)
|
|
167
167
|
}
|
|
168
168
|
"click" => {
|
|
169
|
-
|
|
169
|
+
// `--at X,Y` clicks raw viewport pixels (for canvas / vision tasks with no DOM target);
|
|
170
|
+
// otherwise resolve a normal target (@ref / selector / --text / ...).
|
|
171
|
+
if let Some(at) = f.get("at").and_then(|v| v.as_str()) {
|
|
172
|
+
let mut it = at.split(',');
|
|
173
|
+
let x = it.next().and_then(|s| s.trim().parse::<f64>().ok());
|
|
174
|
+
let y = it.next().and_then(|s| s.trim().parse::<f64>().ok());
|
|
175
|
+
match (x, y) {
|
|
176
|
+
(Some(x), Some(y)) => { p.insert("x".into(), json!(x)); p.insert("y".into(), json!(y)); }
|
|
177
|
+
_ => return None,
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
p.insert("selector".into(), json!(sel(0)));
|
|
181
|
+
}
|
|
170
182
|
if f.contains_key("double") { p.insert("double".into(), json!(true)); }
|
|
171
183
|
req("click", p)
|
|
172
184
|
}
|
|
@@ -39,6 +39,13 @@ async fn dispatch(page: &Page, session_id: &str, action: &str, params: &Map<Stri
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/// True if an action failed specifically because a CDP command timed out (a wedged renderer), which is
|
|
43
|
+
/// recoverable via reload — as opposed to an ordinary action failure (bad selector, JS exception, etc.).
|
|
44
|
+
fn is_cdp_timeout(result: &Value) -> bool {
|
|
45
|
+
result.get("success").and_then(|v| v.as_bool()) == Some(false)
|
|
46
|
+
&& result.get("error").and_then(|v| v.as_str()).map(|e| e.contains("timed out")).unwrap_or(false)
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
pub async fn process(shared: &Shared, request: &Value, shutdown: &tokio::sync::watch::Sender<bool>) -> Value {
|
|
43
50
|
let action = request.get("action").and_then(|v| v.as_str()).unwrap_or("");
|
|
44
51
|
let session_id = request.get("session_id").and_then(|v| v.as_str()).map(String::from);
|
|
@@ -70,7 +77,16 @@ pub async fn process(shared: &Shared, request: &Value, shutdown: &tokio::sync::w
|
|
|
70
77
|
_ => {
|
|
71
78
|
let page = match shared.lock().await.wake(&sid).await { Ok(p) => p, Err(e) => return json!({"success": false, "error": e}) };
|
|
72
79
|
// the manager lock is NOT held while the action runs, so other sessions proceed in parallel
|
|
73
|
-
let result = dispatch(&page, &sid, action, ¶ms, shared).await;
|
|
80
|
+
let mut result = dispatch(&page, &sid, action, ¶ms, shared).await;
|
|
81
|
+
// Wedge recovery: a CDP timeout means the renderer main thread is stuck (commands hang
|
|
82
|
+
// and never recover on their own). Page.reload replaces the renderer with a fresh one;
|
|
83
|
+
// then retry the command ONCE. Skipped for navigate (it already loads a fresh document).
|
|
84
|
+
if is_cdp_timeout(&result) && action != "navigate" {
|
|
85
|
+
if page.recover().await.is_ok() {
|
|
86
|
+
result = dispatch(&page, &sid, action, ¶ms, shared).await;
|
|
87
|
+
if let Some(o) = result.as_object_mut() { o.insert("recovered".into(), json!(true)); }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
74
90
|
let title = page.title().await;
|
|
75
91
|
let mut m = shared.lock().await;
|
|
76
92
|
if let Some(s) = m.sessions.get_mut(&sid) { s.title = title; }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|