browser4-cli 0.1.5 → 0.1.6
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.
- package/README.md +370 -0
- package/bin/.install-method +1 -0
- package/bin/browser4-cli-linux-x64 +0 -0
- package/bin/browser4-cli-win32-x64.exe +0 -0
- package/bin/browser4-cli.js +36 -6
- package/package.json +7 -4
- package/scripts/postinstall.js +48 -35
- package/bin/browser4-cli-darwin-arm64 +0 -0
- package/bin/browser4-cli-darwin-x64 +0 -0
- package/bin/browser4-cli-linux-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-arm64 +0 -0
- package/bin/browser4-cli-linux-musl-x64 +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Browser4 CLI
|
|
2
|
+
|
|
3
|
+
Make websites accessible for AI agents. Automate tasks online with ease.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Global Installation (recommended)
|
|
8
|
+
|
|
9
|
+
Installs the native Rust binary:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g browser4-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
After installation, use `browser4-cli`. The shorter `browser4` command remains
|
|
16
|
+
available as a compatibility alias.
|
|
17
|
+
|
|
18
|
+
### Project Installation (local dependency)
|
|
19
|
+
|
|
20
|
+
For projects that want to pin the version in `package.json`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install browser4-cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then use via `package.json` scripts or by invoking `browser4-cli` directly.
|
|
27
|
+
|
|
28
|
+
### Homebrew (macOS)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
brew install browser4-cli
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Cargo (Rust)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cargo install browser4-cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### From Source
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/platonai/browser4-cli
|
|
44
|
+
cd cli/browser4-cli
|
|
45
|
+
pnpm install
|
|
46
|
+
pnpm build:native # Requires Rust (https://rustup.rs)
|
|
47
|
+
pnpm link --global # Makes browser4-cli available globally
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Requirements
|
|
51
|
+
|
|
52
|
+
- **Chrome** - Latest Chrome installed on your system.
|
|
53
|
+
- **Java 17+** - Required to run the Browser4 backend (`Browser4.jar`).
|
|
54
|
+
- **Rust** - Only needed when building from source (see From Source above).
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
browser4-cli <command> [args] [options]
|
|
70
|
+
browser4-cli -s=<session> <command> [args] [options]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Global options
|
|
74
|
+
|
|
75
|
+
| Flag | Description |
|
|
76
|
+
|--------------------|------------------------------------------------|
|
|
77
|
+
| `--help [command]` | Print help (optionally for a specific command) |
|
|
78
|
+
| `--version` | Print version |
|
|
79
|
+
| `-s=<name>` | Named session label |
|
|
80
|
+
| `--server=<url>` | Override Browser4 server URL |
|
|
81
|
+
|
|
82
|
+
Sessions are persisted independently per name. Omitting `-s` uses the
|
|
83
|
+
default session (`~/.browser4/cli-state.json`). With `-s=<name>`, a
|
|
84
|
+
separate state file is stored under `~/.browser4/sessions/<name>.json`.
|
|
85
|
+
`open` without `-s` reuses the default session if one exists; with
|
|
86
|
+
`-s=<name>` it switches to or creates the named session.
|
|
87
|
+
|
|
88
|
+
### Commands
|
|
89
|
+
|
|
90
|
+
The tables below mirror the commands surfaced by the global `browser4-cli help` overview.
|
|
91
|
+
|
|
92
|
+
#### Core
|
|
93
|
+
|
|
94
|
+
| Command | Description |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `open [url]` | Open or switch to a browser session (optionally navigate to URL) |
|
|
97
|
+
| `close` | Close the active session |
|
|
98
|
+
| `goto <url>` | Navigate to a URL using the current active session |
|
|
99
|
+
| `click <ref> [button]` | Click an element |
|
|
100
|
+
| `dblclick <ref> [button]` | Double-click an element |
|
|
101
|
+
| `type <text> [ref]` | Type text into the focused element or an optional target element |
|
|
102
|
+
| `fill <ref> <text>` | Fill text into an editable element |
|
|
103
|
+
| `hover <ref>` | Hover over an element |
|
|
104
|
+
| `select <ref> <val>` | Select an option in a dropdown |
|
|
105
|
+
| `upload <ref> <file>` | Upload a file |
|
|
106
|
+
| `check <ref>` | Check a checkbox or radio button |
|
|
107
|
+
| `uncheck <ref>` | Uncheck a checkbox or radio button |
|
|
108
|
+
| `drag <startRef> <endRef>` | Drag and drop between two elements |
|
|
109
|
+
| `snapshot` | Capture accessibility snapshot |
|
|
110
|
+
| `eval <expression> [ref]` | Evaluate JavaScript on the page or a target element |
|
|
111
|
+
| `dialog-accept [prompt]` | Accept a dialog |
|
|
112
|
+
| `dialog-dismiss` | Dismiss a dialog |
|
|
113
|
+
| `resize <w> <h>` | Resize the browser window |
|
|
114
|
+
| `delete-data` | Delete session data |
|
|
115
|
+
|
|
116
|
+
#### Navigation
|
|
117
|
+
|
|
118
|
+
| Command | Description |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `go-back` | Go back to the previous page |
|
|
121
|
+
| `go-forward` | Go forward to the next page |
|
|
122
|
+
| `reload` | Reload the current page |
|
|
123
|
+
|
|
124
|
+
#### Keyboard
|
|
125
|
+
|
|
126
|
+
| Command | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `press <key> [ref]` | Press a key on the focused element or an optional target element |
|
|
129
|
+
| `keydown <key>` | Press and hold a key |
|
|
130
|
+
| `keyup <key>` | Release a key |
|
|
131
|
+
|
|
132
|
+
#### Mouse
|
|
133
|
+
|
|
134
|
+
| Command | Description |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `mousemove <x> <y>` | Move mouse to coordinates |
|
|
137
|
+
| `mousedown [button]` | Press mouse button |
|
|
138
|
+
| `mouseup [button]` | Release mouse button |
|
|
139
|
+
| `mousewheel <dx> <dy>` | Scroll the mouse wheel |
|
|
140
|
+
|
|
141
|
+
#### Save as
|
|
142
|
+
|
|
143
|
+
| Command | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `screenshot [ref]` | Take a screenshot |
|
|
146
|
+
| `pdf` | Save page as PDF |
|
|
147
|
+
|
|
148
|
+
#### Tabs
|
|
149
|
+
|
|
150
|
+
| Command | Description |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `tab-list` | List all tabs |
|
|
153
|
+
| `tab-new [url]` | Create a new tab |
|
|
154
|
+
| `tab-close [index]` | Close a browser tab by zero-based index |
|
|
155
|
+
| `tab-select <index>` | Select a browser tab by zero-based index |
|
|
156
|
+
|
|
157
|
+
Use `tab-list` first to find the zero-based tab index you want to select or close.
|
|
158
|
+
|
|
159
|
+
#### Browser sessions
|
|
160
|
+
|
|
161
|
+
| Command | Description |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `list` | List browser sessions |
|
|
164
|
+
| `close-all` | Close all browser sessions without stopping `Browser4.jar` / the Browser4 backend |
|
|
165
|
+
| `kill-all` | Forcefully stop `Browser4.jar` / the Browser4 backend and kill Browser4 browser processes |
|
|
166
|
+
|
|
167
|
+
Use `close-all` for session cleanup when you want to keep the current Browser4 service running. Use `kill-all` only when you explicitly want to stop the backend and clean up tracked Browser4 processes.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
### Advanced commands
|
|
171
|
+
|
|
172
|
+
These commands are intentionally omitted from the global `browser4-cli help` overview.
|
|
173
|
+
Query `browser4-cli help <command>` for the exact syntax when you need them.
|
|
174
|
+
|
|
175
|
+
| Command | Description |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `batch [command...]` | Execute multiple commands in one invocation. Only DOM operations are supported (Core, Navigation, Keyboard, Mouse, Export, Tabs categories). Commands like `open`, `close`, `list`, `agent-run`, etc. are not allowed in batch mode. |
|
|
178
|
+
| `console [min-level]` | List console messages |
|
|
179
|
+
| `extract <instruction>` | Extract structured data from the current page |
|
|
180
|
+
| `summarize [instruction]` | Summarize page content using AI |
|
|
181
|
+
| `agent-run <task>` | Run an autonomous agent task |
|
|
182
|
+
| `agent-status <id>` | Check the status of a running agent task |
|
|
183
|
+
| `agent-result <id>` | Get the result of a completed agent task |
|
|
184
|
+
| `co-create` | Create a collective session with parallel browser contexts |
|
|
185
|
+
| `co-submit [url]` | Submit URL(s) or tasks to the active collective session |
|
|
186
|
+
| `co-scrape <url>` | Scrape data from a URL using CSS selectors |
|
|
187
|
+
| `co-status <id>` | Check the status of a collective task |
|
|
188
|
+
| `co-result <id>` | Get the result of a completed collective task |
|
|
189
|
+
|
|
190
|
+
## Element References
|
|
191
|
+
|
|
192
|
+
The `snapshot` command returns an accessibility tree where every interactive
|
|
193
|
+
node is labeled with a short identifier such as `e15`. Pass this identifier
|
|
194
|
+
directly to commands like `click`, `type`, or `press`; the CLI automatically
|
|
195
|
+
converts it to the `backend:15` selector format required by the server.
|
|
196
|
+
|
|
197
|
+
You can also pass plain CSS selectors (e.g. `.my-button`, `#search-input`) or
|
|
198
|
+
fully-qualified `backend:<N>` refs directly.
|
|
199
|
+
|
|
200
|
+
## State Persistence
|
|
201
|
+
|
|
202
|
+
`browser4-cli` persists CLI state between invocations under `~/.browser4` by
|
|
203
|
+
default. Override the root directory with the `BROWSER4_CLI_STATE_DIR`
|
|
204
|
+
environment variable.
|
|
205
|
+
|
|
206
|
+
- Default session state: `~/.browser4/cli-state.json`
|
|
207
|
+
- Named session state (`-s=<name>`): `~/.browser4/sessions/<name>.json`
|
|
208
|
+
|
|
209
|
+
Each state file stores the current Browser4 server URL plus session-scoped
|
|
210
|
+
fields such as:
|
|
211
|
+
|
|
212
|
+
- `sessionId` — active Browser4 session ID
|
|
213
|
+
- `baseUrl` — Browser4 backend URL used by the CLI
|
|
214
|
+
- `activeSelector` — last selector tracked for keyboard restore flows
|
|
215
|
+
- `lastMousePosition` — last pointer coordinates tracked for mouse restore flows
|
|
216
|
+
|
|
217
|
+
### Session state transitions
|
|
218
|
+
|
|
219
|
+
The `with_session()` helper in `src/main.rs` is the central session lifecycle
|
|
220
|
+
gate for commands that require an active Browser4 session.
|
|
221
|
+
|
|
222
|
+
| Situation | Persisted state transition | Result |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| No persisted session | No state change | `require_session()` fails with `No active session. Run "browser4-cli open" first.` |
|
|
225
|
+
| `open` succeeds (no existing session) | `create_session()` writes a fresh state file with new `sessionId`, current `baseUrl`, and clears `activeSelector` / `lastMousePosition` | A new active session becomes the current CLI session |
|
|
226
|
+
| `open` when a saved session exists and the backend still reports it `active` | No state change — keeps the existing `sessionId` | The existing session is reused; subsequent commands target the same session |
|
|
227
|
+
| `open` when a saved session exists but is missing or no longer `active` in the backend | `invalidate_session()` clears the stale saved `sessionId`, `activeSelector`, and `lastMousePosition`, then `create_session()` writes a fresh session | The stale session is refreshed automatically by opening a new one |
|
|
228
|
+
| `open -s=<name>` | Reads/writes the named session state file | Opens, reuses, or refreshes the named session for that slot; subsequent `-s=<name>` commands use the same slot |
|
|
229
|
+
| Command succeeds through `with_session()` | `sessionId` stays unchanged | The command uses the persisted session normally |
|
|
230
|
+
| Command fails because the server reports a stale / expired session and `recover_stale = false` | `invalidate_session()` clears `sessionId`, `activeSelector`, and `lastMousePosition`, while keeping `baseUrl` | The command fails with `Saved session expired. Run "browser4-cli open" first.` |
|
|
231
|
+
| `goto` is invoked but the saved session is missing or no longer `active` in the backend | `invalidate_session()` clears the saved `sessionId`, `activeSelector`, and `lastMousePosition` | The command fails with `No active session for "goto". Run "browser4-cli open" to create or refresh the session first.` |
|
|
232
|
+
| `close` with an active session | `clear_state()` removes only the current session state file after best-effort remote close | The selected default or named session is fully cleared |
|
|
233
|
+
| `close` with no persisted `sessionId` | `clear_state()` best-effort removes the current session slot | Prints `No active session. Run "browser4-cli open" first.` and exits successfully as a no-op |
|
|
234
|
+
| `close-all` / `kill-all` | `clear_all_state()` removes the default state file and all named session files | All persisted CLI session files are cleared |
|
|
235
|
+
|
|
236
|
+
Notes:
|
|
237
|
+
|
|
238
|
+
- `goto` reuses only the current backend-`active` session. It does not create a
|
|
239
|
+
new session automatically; run `browser4-cli open` first if the saved session
|
|
240
|
+
is missing or stale.
|
|
241
|
+
- `open` first checks whether the saved session for the current slot is still
|
|
242
|
+
backend-`active`. It reuses active sessions and refreshes stale ones by
|
|
243
|
+
creating a new session for the same slot.
|
|
244
|
+
- `list` reads persisted session files and compares them with live backend
|
|
245
|
+
sessions to show both the current status (`Active`, `Stale`, or `Unknown`)
|
|
246
|
+
and whether the next `open` will `Reuse` or `Refresh` that slot.
|
|
247
|
+
|
|
248
|
+
## Runtime Temp Files
|
|
249
|
+
|
|
250
|
+
`browser4-cli` keeps ephemeral runtime artifacts under the system temp directory:
|
|
251
|
+
|
|
252
|
+
- Windows: `%TEMP%\.browser4\browser4-cli`
|
|
253
|
+
- Linux/macOS: `${TMPDIR:-/tmp}/.browser4/browser4-cli`
|
|
254
|
+
|
|
255
|
+
This temp subtree contains items such as:
|
|
256
|
+
|
|
257
|
+
- startup logs for auto-started Browser4 servers
|
|
258
|
+
- staged Maven wrapper launchers
|
|
259
|
+
- Rust test scratch directories used by `browser4-cli` tests
|
|
260
|
+
|
|
261
|
+
Persistent CLI state and the fallback `Browser4.jar` remain under `~/.browser4` by default.
|
|
262
|
+
|
|
263
|
+
## Snapshots
|
|
264
|
+
|
|
265
|
+
After each command that modifies browser state, the CLI automatically:
|
|
266
|
+
|
|
267
|
+
1. Retrieves the current page URL and title
|
|
268
|
+
2. Captures an accessibility snapshot
|
|
269
|
+
3. Saves the snapshot to `.browser4-cli/snapshot/page-<timestamp>.yml`
|
|
270
|
+
4. Prints the snapshot path in Markdown link format
|
|
271
|
+
|
|
272
|
+
## Examples
|
|
273
|
+
|
|
274
|
+
```shell
|
|
275
|
+
# Open a new browser window
|
|
276
|
+
browser4-cli open
|
|
277
|
+
|
|
278
|
+
# Navigate to a page with the current active session
|
|
279
|
+
browser4-cli goto https://playwright.dev
|
|
280
|
+
|
|
281
|
+
# Inspect the page — note the eN labels on interactive nodes
|
|
282
|
+
browser4-cli snapshot
|
|
283
|
+
|
|
284
|
+
# Interact using refs from the snapshot
|
|
285
|
+
browser4-cli click e15
|
|
286
|
+
browser4-cli type "Hello World" e15
|
|
287
|
+
browser4-cli press Enter e15
|
|
288
|
+
browser4-cli eval "document.title"
|
|
289
|
+
browser4-cli eval "element => element.textContent" e15
|
|
290
|
+
browser4-cli keydown Shift
|
|
291
|
+
browser4-cli mousemove 150 300
|
|
292
|
+
browser4-cli mousewheel 0 100
|
|
293
|
+
browser4-cli keyup Shift
|
|
294
|
+
|
|
295
|
+
# Take a screenshot and save it to disk
|
|
296
|
+
browser4-cli screenshot
|
|
297
|
+
|
|
298
|
+
# Inspect tab indices before switching tabs
|
|
299
|
+
browser4-cli tab-list
|
|
300
|
+
browser4-cli tab-select 1
|
|
301
|
+
browser4-cli tab-close 1
|
|
302
|
+
|
|
303
|
+
# Use a custom server URL
|
|
304
|
+
browser4-cli open --server http://localhost:9090
|
|
305
|
+
|
|
306
|
+
# Advanced: execute multiple commands in one process (batch mode)
|
|
307
|
+
# Batch mode only supports DOM operations. You must run `open` separately first.
|
|
308
|
+
browser4-cli open
|
|
309
|
+
browser4-cli batch "goto https://playwright.dev" "snapshot"
|
|
310
|
+
|
|
311
|
+
# Advanced: stop on the first batch failure
|
|
312
|
+
browser4-cli batch --bail "goto https://playwright.dev" "click e1" "screenshot"
|
|
313
|
+
|
|
314
|
+
# Advanced: batch mode for form filling (recommended use case)
|
|
315
|
+
browser4-cli batch "fill e1 'John Doe'" "fill e2 'john@example.com'" "click e3"
|
|
316
|
+
|
|
317
|
+
# Advanced: pipe batch commands as JSON via stdin
|
|
318
|
+
echo '[
|
|
319
|
+
["goto", "https://example.com/form-filling"],
|
|
320
|
+
["click", "#reset-btn"],
|
|
321
|
+
["fill", "#first-name", "Bob"],
|
|
322
|
+
["fill", "#last-name", "Smith"],
|
|
323
|
+
["fill", "#email", "bob@example.com"],
|
|
324
|
+
["select", "#country", "uk"],
|
|
325
|
+
["check", "#agree-terms"],
|
|
326
|
+
["click", "#submit-btn"]
|
|
327
|
+
]' | browser4-cli batch --json
|
|
328
|
+
|
|
329
|
+
# Close the session when done
|
|
330
|
+
browser4-cli close
|
|
331
|
+
|
|
332
|
+
# Close all sessions but keep the current Browser4 backend running
|
|
333
|
+
browser4-cli close-all
|
|
334
|
+
|
|
335
|
+
# Explicitly stop the Browser4 backend and clean up tracked Browser4 processes
|
|
336
|
+
browser4-cli kill-all
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Architecture
|
|
340
|
+
|
|
341
|
+
The Rust CLI is structured as follows:
|
|
342
|
+
|
|
343
|
+
| Module | Purpose |
|
|
344
|
+
|---|---|
|
|
345
|
+
| `main.rs` | Entry point, command dispatch, session management |
|
|
346
|
+
| `args.rs` | CLI argument parsing (global flags, positional args, options) |
|
|
347
|
+
| `commands.rs` | Command definitions mapping to MCP tool names and parameters |
|
|
348
|
+
| `http.rs` | HTTP client for calling `/mcp/call-tool` |
|
|
349
|
+
| `state.rs` | Persistent state management for the default state file and named session files under `~/.browser4/` |
|
|
350
|
+
| `daemon.rs` | Local server auto-start (prefer Maven from repo root, fall back to jar) and health checking |
|
|
351
|
+
| `managed_processes.rs` | Registry for browser4 server processes |
|
|
352
|
+
| `snapshot.rs` | Snapshot and screenshot file helpers |
|
|
353
|
+
| `help.rs` | Help text generation |
|
|
354
|
+
|
|
355
|
+
## Testing
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
## Run all tests (unit + end-to-end):
|
|
359
|
+
cargo test
|
|
360
|
+
|
|
361
|
+
## Run only the end-to-end tests and print their output:
|
|
362
|
+
cargo test --test e2e -- --nocapture
|
|
363
|
+
|
|
364
|
+
## Run a specific end-to-end test scenario:
|
|
365
|
+
cargo test --test e2e -- --nocapture --scenario=test_e2e_batch_form_submission
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## License
|
|
369
|
+
|
|
370
|
+
Apache-2.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm
|
|
Binary file
|
|
Binary file
|
package/bin/browser4-cli.js
CHANGED
|
@@ -2,18 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import {
|
|
5
|
+
import { arch } from "os";
|
|
6
|
+
import { execSync, spawn } from "child_process";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const scriptDir = path.dirname(__filename);
|
|
10
|
-
const exeName = process.platform === "win32" ? "browser4-cli.exe" : "browser4-cli";
|
|
11
|
-
const exePath = path.join(scriptDir, "target", "release", exeName);
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
function isMusl() {
|
|
13
|
+
if (process.platform !== "linux") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const result = execSync("ldd --version 2>&1 || true", { encoding: "utf8" });
|
|
19
|
+
return result.toLowerCase().includes("musl");
|
|
20
|
+
} catch {
|
|
21
|
+
return fs.existsSync("/lib/ld-musl-x86_64.so.1") || fs.existsSync("/lib/ld-musl-aarch64.so.1");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveExecutableCandidates() {
|
|
26
|
+
const platformKey = `${process.platform === "linux" && isMusl() ? "linux-musl" : process.platform}-${arch()}`;
|
|
27
|
+
const nativeBinaryName = `browser4-cli-${platformKey}${process.platform === "win32" ? ".exe" : ""}`;
|
|
28
|
+
const sourceBinaryName = `browser4-cli${process.platform === "win32" ? ".exe" : ""}`;
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
path.join(scriptDir, nativeBinaryName),
|
|
32
|
+
path.join(scriptDir, "target", "release", sourceBinaryName),
|
|
33
|
+
path.join(scriptDir, "..", "browser4-cli", "target", "release", sourceBinaryName),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const candidatePaths = resolveExecutableCandidates();
|
|
38
|
+
const exePath = candidatePaths.find((candidate) => fs.existsSync(candidate));
|
|
39
|
+
|
|
40
|
+
if (!exePath) {
|
|
14
41
|
const scriptName = path.basename(__filename);
|
|
15
|
-
console.error(`[${scriptName}] ERROR: executable not found
|
|
16
|
-
|
|
42
|
+
console.error(`[${scriptName}] ERROR: executable not found. Checked:`);
|
|
43
|
+
for (const candidate of candidatePaths) {
|
|
44
|
+
console.error(` - ${candidate}`);
|
|
45
|
+
}
|
|
46
|
+
console.error(`[${scriptName}] Run: npm install browser4-cli or cargo build --release --manifest-path cli/browser4-cli/Cargo.toml`);
|
|
17
47
|
process.exit(1);
|
|
18
48
|
}
|
|
19
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser4-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Browser automation CLI for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"skills"
|
|
11
11
|
],
|
|
12
12
|
"bin": {
|
|
13
|
+
"browser4-cli": "./bin/browser4-cli.js",
|
|
13
14
|
"browser4": "./bin/browser4-cli.js"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
@@ -27,12 +28,12 @@
|
|
|
27
28
|
"keywords": [
|
|
28
29
|
"browser",
|
|
29
30
|
"browser4",
|
|
31
|
+
"browser4-cli",
|
|
30
32
|
"automation",
|
|
31
33
|
"headless",
|
|
32
34
|
"chrome",
|
|
33
35
|
"cdp",
|
|
34
36
|
"cli",
|
|
35
|
-
"browser4-cli",
|
|
36
37
|
"agent"
|
|
37
38
|
],
|
|
38
39
|
"license": "Apache-2.0",
|
|
@@ -44,6 +45,8 @@
|
|
|
44
45
|
"url": "https://github.com/platonai/Browser4/issues"
|
|
45
46
|
},
|
|
46
47
|
"homepage": "https://browser4.io",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
48
|
+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"browser4-cli": "^0.1.5"
|
|
51
|
+
}
|
|
49
52
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -43,6 +43,7 @@ const packageJson = JSON.parse(
|
|
|
43
43
|
(await import('fs')).readFileSync(join(projectRoot, 'package.json'), 'utf8')
|
|
44
44
|
);
|
|
45
45
|
const version = packageJson.version;
|
|
46
|
+
const commandNames = Object.keys(packageJson.bin || { 'browser4-cli': './bin/browser4-cli.js' });
|
|
46
47
|
|
|
47
48
|
// GitHub release URL
|
|
48
49
|
const GITHUB_REPO = 'platonai/Browser4';
|
|
@@ -202,12 +203,12 @@ function showInstallReminder() {
|
|
|
202
203
|
console.log(' ⚠ No Chrome installation detected.');
|
|
203
204
|
console.log(' If you plan to use a local browser, run:');
|
|
204
205
|
console.log('');
|
|
205
|
-
console.log(' browser4 install');
|
|
206
|
+
console.log(' browser4-cli install');
|
|
206
207
|
if (platform() === 'linux') {
|
|
207
208
|
console.log('');
|
|
208
209
|
console.log(' On Linux, include system dependencies with:');
|
|
209
210
|
console.log('');
|
|
210
|
-
console.log(' browser4 install --with-deps');
|
|
211
|
+
console.log(' browser4-cli install --with-deps');
|
|
211
212
|
}
|
|
212
213
|
console.log('');
|
|
213
214
|
console.log(' You can skip this if you use --cdp, --provider, --engine, or --executable-path.');
|
|
@@ -240,27 +241,31 @@ async function fixUnixSymlink() {
|
|
|
240
241
|
return; // npm not available
|
|
241
242
|
}
|
|
242
243
|
|
|
243
|
-
|
|
244
|
+
let optimized = false;
|
|
245
|
+
for (const commandName of commandNames) {
|
|
246
|
+
const symlinkPath = join(npmBinDir, commandName);
|
|
244
247
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
try {
|
|
249
|
+
const stat = lstatSync(symlinkPath);
|
|
250
|
+
if (!stat.isSymbolicLink()) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
unlinkSync(symlinkPath);
|
|
259
|
+
symlinkSync(binaryPath, symlinkPath);
|
|
260
|
+
optimized = true;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
console.log(`⚠ Could not optimize symlink for ${commandName}: ${err.message}`);
|
|
263
|
+
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
250
264
|
}
|
|
251
|
-
} catch {
|
|
252
|
-
return; // Symlink doesn't exist, not a global install
|
|
253
265
|
}
|
|
254
266
|
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
unlinkSync(symlinkPath);
|
|
258
|
-
symlinkSync(binaryPath, symlinkPath);
|
|
267
|
+
if (optimized) {
|
|
259
268
|
console.log('✓ Optimized: symlink points to native binary (zero overhead)');
|
|
260
|
-
} catch (err) {
|
|
261
|
-
// Permission error or other issue - not critical, JS wrapper still works
|
|
262
|
-
console.log(`⚠ Could not optimize symlink: ${err.message}`);
|
|
263
|
-
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
264
269
|
}
|
|
265
270
|
}
|
|
266
271
|
|
|
@@ -277,19 +282,9 @@ async function fixWindowsShims() {
|
|
|
277
282
|
return;
|
|
278
283
|
}
|
|
279
284
|
|
|
280
|
-
const cmdShim = join(npmBinDir, 'browser4.cmd');
|
|
281
|
-
const ps1Shim = join(npmBinDir, 'browser4.ps1');
|
|
282
|
-
|
|
283
|
-
// Shims may not exist yet during postinstall (npm creates them after
|
|
284
|
-
// lifecycle scripts). If missing, fall back: the JS wrapper at
|
|
285
|
-
// bin/browser4.js handles Windows correctly via child_process.spawn.
|
|
286
|
-
if (!existsSync(cmdShim)) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
285
|
// Detect architecture so ARM64 Windows is handled correctly
|
|
291
286
|
const cpuArch = arch() === 'arm64' ? 'arm64' : 'x64';
|
|
292
|
-
const relativeBinaryPath = `node_modules\\
|
|
287
|
+
const relativeBinaryPath = `node_modules\\${packageJson.name}\\bin\\browser4-cli-win32-${cpuArch}.exe`;
|
|
293
288
|
const absoluteBinaryPath = join(npmBinDir, relativeBinaryPath);
|
|
294
289
|
|
|
295
290
|
// Only rewrite shims if the native binary actually exists
|
|
@@ -298,13 +293,31 @@ async function fixWindowsShims() {
|
|
|
298
293
|
}
|
|
299
294
|
|
|
300
295
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
let optimized = false;
|
|
297
|
+
for (const commandName of commandNames) {
|
|
298
|
+
const cmdShim = join(npmBinDir, `${commandName}.cmd`);
|
|
299
|
+
const ps1Shim = join(npmBinDir, `${commandName}.ps1`);
|
|
300
|
+
|
|
301
|
+
if (!existsSync(cmdShim) && !existsSync(ps1Shim)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (existsSync(cmdShim)) {
|
|
306
|
+
const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
|
|
307
|
+
writeFileSync(cmdShim, cmdContent);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (existsSync(ps1Shim)) {
|
|
311
|
+
const ps1Content = `#!/usr/bin/env pwsh\r\n$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent\r\n& "$basedir\\${relativeBinaryPath}" $args\r\nexit $LASTEXITCODE\r\n`;
|
|
312
|
+
writeFileSync(ps1Shim, ps1Content);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
optimized = true;
|
|
316
|
+
}
|
|
306
317
|
|
|
307
|
-
|
|
318
|
+
if (optimized) {
|
|
319
|
+
console.log('✓ Optimized: shims point to native binary (zero overhead)');
|
|
320
|
+
}
|
|
308
321
|
} catch (err) {
|
|
309
322
|
console.log(`⚠ Could not optimize shims: ${err.message}`);
|
|
310
323
|
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|