browser4-cli 0.1.4 → 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 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
@@ -2,18 +2,48 @@
2
2
 
3
3
  import fs from "fs";
4
4
  import path from "path";
5
- import { spawn } from "child_process";
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
- if (!fs.existsSync(exePath)) {
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: "${exePath}"`);
16
- console.error(`[${scriptName}] Run: cargo build --release (in sdks/browser4-cli)`);
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.4",
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
- "devDependencies": {},
48
- "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
48
+ "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
49
+ "dependencies": {
50
+ "browser4-cli": "^0.1.5"
51
+ }
49
52
  }
@@ -1,5 +1,5 @@
1
1
  #!/bin/bash
2
- set -e
2
+ set -euo pipefail
3
3
 
4
4
  # Build browser4 for all platforms using Docker
5
5
  # Usage: ./scripts/build-all-platforms.sh
@@ -7,6 +7,24 @@ set -e
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
8
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
9
9
  OUTPUT_DIR="$PROJECT_ROOT/bin"
10
+ SKIP_DOCKER_BUILD=false
11
+ DRY_RUN=false
12
+
13
+ while [[ $# -gt 0 ]]; do
14
+ case "$1" in
15
+ --skip-docker-build)
16
+ SKIP_DOCKER_BUILD=true
17
+ ;;
18
+ --dry-run)
19
+ DRY_RUN=true
20
+ ;;
21
+ *)
22
+ echo "Unknown argument: $1" >&2
23
+ exit 1
24
+ ;;
25
+ esac
26
+ shift
27
+ done
10
28
 
11
29
  # Colors
12
30
  RED='\033[0;31m'
@@ -20,9 +38,24 @@ echo ""
20
38
  # Ensure output directory exists
21
39
  mkdir -p "$OUTPUT_DIR"
22
40
 
41
+ run_cmd() {
42
+ if [ "$DRY_RUN" = true ]; then
43
+ echo "[DRY-RUN] $*"
44
+ else
45
+ "$@"
46
+ fi
47
+ }
48
+
49
+ if ! command -v docker >/dev/null 2>&1; then
50
+ echo "Docker CLI not found in PATH. Install Docker and retry." >&2
51
+ exit 1
52
+ fi
53
+
23
54
  # Build the Docker image if needed
24
- echo -e "${YELLOW}Building Docker cross-compilation image...${NC}"
25
- docker build -t browser4-builder -f "$PROJECT_ROOT/docker/Dockerfile.build" "$PROJECT_ROOT"
55
+ if [ "$SKIP_DOCKER_BUILD" = false ]; then
56
+ echo -e "${YELLOW}Building Docker cross-compilation image...${NC}"
57
+ run_cmd docker build -t browser4-builder -f "$PROJECT_ROOT/docker/Dockerfile.build" "$PROJECT_ROOT"
58
+ fi
26
59
 
27
60
  # Function to build for a target
28
61
  build_target() {
@@ -31,12 +64,17 @@ build_target() {
31
64
 
32
65
  echo -e "${YELLOW}Building for ${target}...${NC}"
33
66
 
34
- docker run --rm \
67
+ run_cmd docker run --rm \
35
68
  -v "$PROJECT_ROOT/cli:/build" \
36
69
  -v "$OUTPUT_DIR:/output" \
37
70
  browser4-builder \
38
71
  -c "cargo zigbuild --release --target ${target} && cp /build/target/${target}/release/browser4-cli* /output/${output_name} && chmod +x /output/${output_name} 2>/dev/null || true"
39
72
 
73
+ if [ "$DRY_RUN" = true ]; then
74
+ echo -e "${YELLOW}[DRY-RUN] Skipping artifact verification for ${output_name}${NC}"
75
+ return 0
76
+ fi
77
+
40
78
  if [ -f "$OUTPUT_DIR/$output_name" ]; then
41
79
  echo -e "${GREEN}✓ Built ${output_name}${NC}"
42
80
  else
@@ -71,4 +109,9 @@ echo ""
71
109
  echo -e "${GREEN}Build complete!${NC}"
72
110
  echo ""
73
111
  echo "Binaries are in: $OUTPUT_DIR"
74
- ls -la "$OUTPUT_DIR"/browser4-cli-*
112
+
113
+ if [ "$DRY_RUN" = true ]; then
114
+ echo "[DRY-RUN] Skipping artifact listing."
115
+ else
116
+ ls -la "$OUTPUT_DIR"/browser4-cli-*
117
+ fi
@@ -43,9 +43,10 @@ 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
- const GITHUB_REPO = 'platonai/browser4';
49
+ const GITHUB_REPO = 'platonai/Browser4';
49
50
  const DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${binaryName}`;
50
51
 
51
52
  async function downloadFile(url, dest) {
@@ -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
- const symlinkPath = join(npmBinDir, 'browser4');
244
+ let optimized = false;
245
+ for (const commandName of commandNames) {
246
+ const symlinkPath = join(npmBinDir, commandName);
244
247
 
245
- // Check if symlink exists (indicates global install)
246
- try {
247
- const stat = lstatSync(symlinkPath);
248
- if (!stat.isSymbolicLink()) {
249
- return; // Not a symlink, don't touch it
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
- // Replace symlink to point directly to native binary
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\\browser4\\bin\\browser4-win32-${cpuArch}.exe`;
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
- const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
302
- writeFileSync(cmdShim, cmdContent);
303
-
304
- 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`;
305
- writeFileSync(ps1Shim, ps1Content);
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
- console.log('✓ Optimized: shims point to native binary (zero overhead)');
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)');