browser4-cli 0.1.13 → 0.1.15

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 CHANGED
@@ -61,19 +61,42 @@ the full option reference, supported platforms, and examples.
61
61
 
62
62
  ### From Source
63
63
 
64
+ **Build prerequisites:** Rust (stable, edition 2021), Node.js 24+, pnpm 10+, git.
65
+
64
66
  ```bash
65
- git clone https://github.com/platonai/browser4-cli
66
- cd cli/browser4-cli
67
+ git clone https://github.com/platonai/Browser4.git
68
+ cd Browser4/cli/browser4-cli
67
69
  pnpm install
68
- pnpm build:native # Requires Rust (https://rustup.rs)
70
+ pnpm build:native # Compiles the Rust binary (requires https://rustup.rs)
69
71
  pnpm link --global # Makes browser4-cli available globally
70
72
  ```
71
73
 
74
+ Cross-compilation from Linux (for release builds) additionally requires:
75
+ `cargo-zigbuild`, Zig 0.13.0, `gcc-aarch64-linux-gnu`, and `mingw-w64`.
76
+ See `cli/docker/Dockerfile.build` for a Dockerized build environment.
77
+
72
78
  ### Requirements
73
79
 
74
- - **Chrome** - Latest Chrome installed on your system.
75
- - **Java 17+** - Required to run the Browser4 backend (`Browser4.jar`).
76
- - **Rust** - Only needed when building from source (see From Source above).
80
+ - **Chrome** Latest Chrome installed on your system. The CLI can auto-install Chrome on most platforms when missing.
81
+ - **Java 17+** Required to run the Browser4 backend (`Browser4.jar`). Eclipse Temurin recommended. JDK 21+ enables best jlink compression when the CLI auto-builds a runtime bundle from source.
82
+ - **Rust** Only needed when building the CLI from source (see From Source above). The `stable` toolchain (edition 2021) is sufficient.
83
+
84
+ #### Additional requirements for auto-building a runtime bundle from source
85
+
86
+ When the CLI detects a Browser4 repository checkout, it attempts to build a
87
+ self-contained runtime bundle (bundled JRE + dependency JARs) from source
88
+ instead of downloading a pre-built release. This requires:
89
+
90
+ | Tool | Version | Linux | macOS | Windows |
91
+ |------|---------|-------|-------|---------|
92
+ | **Maven** | 3.9+ | via `mvnw` wrapper | via `mvnw` wrapper | via `mvnw.cmd` wrapper |
93
+ | **JDK tools** (`jdeps`, `jlink`) | bundled with JDK 16+ | included in JDK | included in JDK | included in JDK |
94
+ | **PowerShell 7** (`pwsh`) | 7.0+ | **required** | **required** | built-in (`powershell.exe`) |
95
+ | **tar** | any | **required** | **required** | built-in |
96
+
97
+ Set `BROWSER4_CLI_FORCE_REMOTE_BUNDLE=1` to skip the local build and always
98
+ download a pre-built bundle — useful in CI / corporate environments where
99
+ Maven or jlink are unavailable.
77
100
 
78
101
  ## Usage
79
102
 
@@ -366,7 +389,7 @@ browser4-cli swarm submit https://example.com/direct \
366
389
  --seed-file=./swarm-seeds.txt \
367
390
  --deadline=2026-03-30T00:00:00Z \
368
391
  --expires=1d \
369
- --refresh --parse --store-content
392
+ --refresh --store-content
370
393
 
371
394
  # poll and fetch the result
372
395
  browser4-cli swarm status scrape-task-4
@@ -391,7 +414,7 @@ browser4-cli swarm query "https://www.amazon.com/dp/B08PP5MSVB" --sql "
391
414
  browser4-cli swarm query "https://www.amazon.com/dp/B08PP5MSVB" --sql @query.sql
392
415
 
393
416
  # With seed file and load options:
394
- browser4-cli swarm query --sql @query.sql --seed-file=./urls.txt --refresh --parse
417
+ browser4-cli swarm query --sql @query.sql --seed-file=./urls.txt --refresh
395
418
  ```
396
419
 
397
420
  ### Notes
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser4-cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Browser automation CLI for AI agents",
5
5
  "type": "module",
6
6
  "files": [
@@ -67,9 +67,17 @@ $ErrorActionPreference = "Stop"
67
67
  # OS detection (compatible with PS 5.1+)
68
68
  # ──────────────────────────────────────────────
69
69
 
70
- $script:IsWin = [System.Environment]::OSVersion.Platform -eq "Win32NT"
71
- $script:IsMac = [System.Environment]::OSVersion.Platform -eq "Unix" -and (uname -s 2>$null) -eq "Darwin"
72
- $script:IsLinux = [System.Environment]::OSVersion.Platform -eq "Unix" -and (uname -s 2>$null) -ne "Darwin"
70
+ # Avoid assigning to $IsLinux / $IsWindows / $IsMacOS directly —
71
+ # they are read-only automatic variables in PowerShell 7+.
72
+ if ($PSVersionTable.PSVersion.Major -ge 6) {
73
+ $script:OSWin = $IsWindows
74
+ $script:OSLinux = $IsLinux
75
+ $script:OSMac = $IsMacOS
76
+ } else {
77
+ $script:OSWin = [System.Environment]::OSVersion.Platform -eq "Win32NT"
78
+ $script:OSMac = $false
79
+ $script:OSLinux = $false
80
+ }
73
81
 
74
82
  # ──────────────────────────────────────────────
75
83
  # Helpers
@@ -102,13 +110,13 @@ function Write-WarnMsg {
102
110
  function Get-PlatformKey {
103
111
  $arch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "arm64" } else { "x64" }
104
112
 
105
- if ($script:IsWin) {
113
+ if ($script:OSWin) {
106
114
  return "win32-$arch"
107
115
  }
108
- elseif ($script:IsMac) {
116
+ elseif ($script:OSMac) {
109
117
  return "darwin-$arch"
110
118
  }
111
- elseif ($script:IsLinux) {
119
+ elseif ($script:OSLinux) {
112
120
  # Detect musl
113
121
  $isMusl = $false
114
122
  try {
@@ -133,10 +141,10 @@ function Get-BinaryName {
133
141
  }
134
142
 
135
143
  function Get-DefaultInstallDir {
136
- if ($script:IsWin) {
144
+ if ($script:OSWin) {
137
145
  return Join-Path $env:LOCALAPPDATA "Programs\browser4-cli"
138
146
  }
139
- elseif ($script:IsLinux -or $script:IsMac) {
147
+ elseif ($script:OSLinux -or $script:OSMac) {
140
148
  # Prefer ~/.local/bin for user installs
141
149
  if (Test-Path "$env:HOME/.local/bin") {
142
150
  return "$env:HOME/.local/bin"
@@ -216,6 +224,97 @@ function Invoke-Download {
216
224
  }
217
225
  }
218
226
 
227
+ # ──────────────────────────────────────────────
228
+ # Symlinks
229
+ # ──────────────────────────────────────────────
230
+
231
+ function New-PlatformLink {
232
+ param([string]$LinkPath, [string]$TargetName, [string]$DisplayName)
233
+
234
+ # Try symbolic link first (works on Unix; on Windows needs Admin or Developer Mode)
235
+ try {
236
+ New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetName -Force -ErrorAction Stop | Out-Null
237
+ Write-Check "Created symlink: $DisplayName -> $TargetName"
238
+ return $true
239
+ } catch {
240
+ # Symbolic link failed — try hard link on Windows
241
+ }
242
+
243
+ # Try hard link (Windows, same volume)
244
+ if ($script:OSWin) {
245
+ try {
246
+ $targetPath = Join-Path (Split-Path $LinkPath -Parent) $TargetName
247
+ New-Item -ItemType HardLink -Path $LinkPath -Target $targetPath -Force -ErrorAction Stop | Out-Null
248
+ Write-Check "Created hard link: $DisplayName -> $TargetName"
249
+ return $true
250
+ } catch {
251
+ # Hard link also failed — create a .cmd wrapper as last resort
252
+ }
253
+
254
+ # Last resort: .cmd wrapper that forwards all arguments
255
+ try {
256
+ $wrapperContent = '@"%~dp0' + $TargetName + '" %*'
257
+ Set-Content -Path ($LinkPath -replace '\.exe$', '.cmd') -Value $wrapperContent -Force -ErrorAction Stop
258
+ Write-Check "Created wrapper: " + (Split-Path ($LinkPath -replace '\.exe$', '.cmd') -Leaf) + " -> $TargetName"
259
+ return $true
260
+ } catch {
261
+ Write-WarnMsg "Could not create link for $DisplayName (may need admin privileges)"
262
+ return $false
263
+ }
264
+ }
265
+
266
+ Write-WarnMsg "Could not create link for $DisplayName"
267
+ return $false
268
+ }
269
+
270
+ function New-Symlinks {
271
+ param([string]$BinaryName, [string]$InstallDir, [string]$PlatformKey)
272
+
273
+ $ext = if ($PlatformKey.StartsWith("win32")) { ".exe" } else { "" }
274
+
275
+ # 1) Always: browser4-cli -> browser4-cli-<platform>
276
+ $linkName = "browser4-cli$ext"
277
+ $linkPath = Join-Path $InstallDir $linkName
278
+
279
+ if ($DryRun) {
280
+ Write-Step "[DRY-RUN] Would create link: $linkName -> $BinaryName"
281
+ } else {
282
+ New-PlatformLink -LinkPath $linkPath -TargetName $BinaryName -DisplayName $linkName
283
+ }
284
+
285
+ # 2) Only if no conflict: b4 -> browser4-cli-<platform>
286
+ $shortName = "b4$ext"
287
+ $shortPath = Join-Path $InstallDir $shortName
288
+
289
+ # Check if b4 is already on PATH (conflict with another tool)
290
+ $existingCmd = Get-Command b4 -ErrorAction SilentlyContinue
291
+ if ($existingCmd) {
292
+ Write-WarnMsg "Skipping short link '$shortName': 'b4' already found on PATH ($($existingCmd.Source))"
293
+ return
294
+ }
295
+
296
+ # Check if b4 already exists in the install directory
297
+ if (Test-Path $shortPath) {
298
+ Write-WarnMsg "Skipping short link '$shortName': already exists in $InstallDir"
299
+ return
300
+ }
301
+
302
+ # Also check b4.cmd if on Windows (wrapper fallback)
303
+ if ($script:OSWin) {
304
+ $shortCmdPath = Join-Path $InstallDir "b4.cmd"
305
+ if (Test-Path $shortCmdPath) {
306
+ Write-WarnMsg "Skipping short link '$shortName': 'b4.cmd' already exists in $InstallDir"
307
+ return
308
+ }
309
+ }
310
+
311
+ if ($DryRun) {
312
+ Write-Step "[DRY-RUN] Would create link: $shortName -> $BinaryName"
313
+ } else {
314
+ New-PlatformLink -LinkPath $shortPath -TargetName $BinaryName -DisplayName $shortName
315
+ }
316
+ }
317
+
219
318
  # ──────────────────────────────────────────────
220
319
  # PATH management
221
320
  # ──────────────────────────────────────────────
@@ -324,17 +423,21 @@ Please check:
324
423
  }
325
424
 
326
425
  # On Unix, ensure executable bit
327
- if (-not $script:IsWin) {
426
+ if (-not $script:OSWin) {
328
427
  if (-not $DryRun) {
329
428
  try { chmod +x $binaryPath 2>$null } catch { }
330
429
  }
331
430
  }
332
431
 
432
+ # Create symlinks (browser4-cli -> platform binary, b4 if no conflict)
433
+ Write-Summary ""
434
+ New-Symlinks -BinaryName $binaryName -InstallDir $installDir -PlatformKey $platformKey
435
+
333
436
  # Add to PATH
334
- if ($AddToPath -and $script:IsWin) {
437
+ if ($AddToPath -and $script:OSWin) {
335
438
  Write-Summary ""
336
439
  Add-DirectoryToUserPath -Dir $installDir
337
- } elseif ($AddToPath -and -not $script:IsWin) {
440
+ } elseif ($AddToPath -and -not $script:OSWin) {
338
441
  Write-Summary ""
339
442
  $shellRc = if (Test-Path "$env:HOME/.zshrc") { "$env:HOME/.zshrc" } elseif (Test-Path "$env:HOME/.bashrc") { "$env:HOME/.bashrc" } elseif (Test-Path "$env:HOME/.bash_profile") { "$env:HOME/.bash_profile" } else { "$env:HOME/.profile" }
340
443
  $pathLine = "export PATH=""$installDir`:`$PATH"""
@@ -370,7 +473,7 @@ Please check:
370
473
  Write-Summary ""
371
474
  Write-Summary "Run 'browser4-cli --help' to get started." -Color Cyan
372
475
 
373
- if ($script:IsWin) {
476
+ if ($script:OSWin) {
374
477
  Write-Summary "If the command isn't found, restart your terminal or run:"
375
478
  Write-Summary " `$env:Path = [System.Environment]::GetEnvironmentVariable('Path','User') + ';' + [System.Environment]::GetEnvironmentVariable('Path','Machine')"
376
479
  }
@@ -307,6 +307,52 @@ add_to_shell_rc() {
307
307
  say " Reload with: source $rc_file"
308
308
  }
309
309
 
310
+ # ──────────────────────────────────────────────
311
+ # Symlinks
312
+ # ──────────────────────────────────────────────
313
+
314
+ create_symlinks() {
315
+ local binary_name="$1"
316
+ local install_dir="$2"
317
+
318
+ local ext=""
319
+ if [[ "$binary_name" == *.exe ]]; then
320
+ ext=".exe"
321
+ fi
322
+
323
+ # 1) Always: browser4-cli -> browser4-cli-<platform>
324
+ local link_name="browser4-cli${ext}"
325
+ local link_path="${install_dir}/${link_name}"
326
+
327
+ if [[ "$DRY_RUN" == true ]]; then
328
+ step "[DRY-RUN] Would create symlink: ${link_name} -> ${binary_name}"
329
+ else
330
+ ln -sf "$binary_name" "$link_path"
331
+ ok "Created symlink: ${link_name} -> ${binary_name}"
332
+ fi
333
+
334
+ # 2) Only if no conflict: b4 -> browser4-cli-<platform>
335
+ local short_name="b4${ext}"
336
+ local short_path="${install_dir}/${short_name}"
337
+
338
+ if command -v b4 >/dev/null 2>&1; then
339
+ warn "Skipping short link '${short_name}': 'b4' already found on PATH"
340
+ return
341
+ fi
342
+
343
+ if [[ -e "$short_path" ]] || [[ -L "$short_path" ]]; then
344
+ warn "Skipping short link '${short_name}': already exists in ${install_dir}"
345
+ return
346
+ fi
347
+
348
+ if [[ "$DRY_RUN" == true ]]; then
349
+ step "[DRY-RUN] Would create symlink: ${short_name} -> ${binary_name}"
350
+ else
351
+ ln -sf "$binary_name" "$short_path"
352
+ ok "Created symlink: ${short_name} -> ${binary_name}"
353
+ fi
354
+ }
355
+
310
356
  # ──────────────────────────────────────────────
311
357
  # Main
312
358
  # ──────────────────────────────────────────────
@@ -399,6 +445,10 @@ main() {
399
445
  chmod +x "$binary_path" 2>/dev/null || true
400
446
  fi
401
447
 
448
+ # Create symlinks (browser4-cli -> platform binary, b4 if no conflict)
449
+ say ""
450
+ create_symlinks "$binary_name" "$INSTALL_DIR"
451
+
402
452
  # Add to PATH
403
453
  if [[ "$ADD_TO_PATH" == true ]]; then
404
454
  say ""
@@ -9,6 +9,9 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
9
9
  export const cliRootDir = join(__dirname, '..');
10
10
  const packageJsonPath = join(cliRootDir, 'package.json');
11
11
 
12
+ /** The only valid npm package name for the CLI. */
13
+ const EXPECTED_PACKAGE_NAME = 'browser4-cli';
14
+
12
15
  /**
13
16
  * Reads browser4-cli package metadata.
14
17
  *
@@ -16,6 +19,15 @@ const packageJsonPath = join(cliRootDir, 'package.json');
16
19
  */
17
20
  export function readPackageMetadata() {
18
21
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
22
+
23
+ if (packageJson.name !== EXPECTED_PACKAGE_NAME) {
24
+ console.error(
25
+ `ERROR: cli/package.json name is "${packageJson.name}", expected "${EXPECTED_PACKAGE_NAME}". ` +
26
+ `Refusing to proceed with an unexpected package name.`
27
+ );
28
+ process.exit(1);
29
+ }
30
+
19
31
  return {
20
32
  name: packageJson.name,
21
33
  version: packageJson.version,
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Syncs the CLI version from cli/package.json to the Cargo manifest.
4
+ * Syncs the CLI version from cli/VERSION-CLI to all dependent files.
5
5
  *
6
- * cli/package.json is the single source of truth for the CLI version. This
6
+ * cli/VERSION-CLI is the single source of truth for the CLI version. This
7
7
  * allows the backend Maven project and the CLI to be published separately with
8
8
  * different versions. This script:
9
- * - Reads the version from cli/package.json
10
- * - Strips the Maven-style "-SNAPSHOT" suffix
9
+ * - Reads the version from cli/VERSION-CLI
10
+ * - Strips any "-SNAPSHOT" suffix (if present)
11
11
  * - Fetches the latest published version from npm and compares it
12
12
  * against the local version, warning when the bump is neither a
13
13
  * patch nor a minor increment.
14
- * - Writes the clean semver to cli/browser4-cli/Cargo.toml
14
+ * - Writes the version to cli/package.json
15
+ * - Writes the version to cli/browser4-cli/Cargo.toml
15
16
  * - Updates Cargo.lock to match
16
17
  *
17
18
  * Usage:
@@ -126,19 +127,26 @@ function checkVersionBump(published, local) {
126
127
 
127
128
  const checkOnly = process.argv.includes("--check");
128
129
 
129
- // 1. Read the CLI version from cli/package.json (the source of truth)
130
- const packageJsonPath = join(cliDir, "package.json");
131
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
132
- const pkgName = packageJson.name;
133
- const pkgVersion = packageJson.version;
130
+ // The npm package name (hard-coded stable across versions)
131
+ const pkgName = "browser4-cli";
134
132
 
135
- if (!pkgVersion) {
136
- console.error("ERROR: cli/package.json does not contain a version field.");
133
+ // 1. Read the CLI version from cli/VERSION-CLI (the single source of truth)
134
+ const versionCliPath = join(cliDir, "VERSION-CLI");
135
+ let versionRaw;
136
+ try {
137
+ versionRaw = readFileSync(versionCliPath, "utf-8").trim();
138
+ } catch {
139
+ console.error(`ERROR: Cannot read ${versionCliPath}`);
137
140
  process.exit(1);
138
141
  }
139
142
 
140
- const version = stripSnapshot(pkgVersion);
141
- if (checkOnly) console.log(`cli/package.json: ${version}`);
143
+ if (!versionRaw) {
144
+ console.error("ERROR: cli/VERSION-CLI is empty.");
145
+ process.exit(1);
146
+ }
147
+
148
+ const version = stripSnapshot(versionRaw);
149
+ if (checkOnly) console.log(`cli/VERSION-CLI: ${version}`);
142
150
 
143
151
  // 2. Compare against the latest published version on npm
144
152
  const publishedVersion = getLatestPublishedVersion(pkgName);
@@ -154,7 +162,34 @@ if (publishedVersion) {
154
162
  console.log(`Latest published: (not found — package may not be published yet)`);
155
163
  }
156
164
 
157
- // 3. Sync cli/browser4-cli/Cargo.toml
165
+ // ---------------------------------------------------------------------------
166
+ // 3. Sync cli/package.json
167
+ // ---------------------------------------------------------------------------
168
+ const packageJsonPath = join(cliDir, "package.json");
169
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
170
+
171
+ if (!packageJson.version) {
172
+ console.error("ERROR: cli/package.json does not contain a version field.");
173
+ process.exit(1);
174
+ }
175
+
176
+ if (packageJson.version !== version) {
177
+ if (checkOnly) {
178
+ console.error(`MISMATCH: ${packageJsonPath} version is "${packageJson.version}", expected "${version}"`);
179
+ process.exitCode = 1;
180
+ } else {
181
+ const oldVersion = packageJson.version;
182
+ packageJson.version = version;
183
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
184
+ console.log(` Updated ${packageJsonPath}: ${oldVersion} -> ${version}`);
185
+ }
186
+ } else {
187
+ if (!checkOnly) console.log(` ${packageJsonPath} already up to date`);
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // 4. Sync cli/browser4-cli/Cargo.toml
192
+ // ---------------------------------------------------------------------------
158
193
  const cargoTomlPath = join(cargoDir, "Cargo.toml");
159
194
  let cargoToml = readFileSync(cargoTomlPath, "utf-8");
160
195
  const cargoVersionRegex = /^version\s*=\s*"[^"]*"/m;
@@ -179,7 +214,9 @@ if (cargoVersion !== version) {
179
214
  if (!checkOnly) console.log(` ${cargoTomlPath} already up to date`);
180
215
  }
181
216
 
182
- // 4. Update Cargo.lock (only in sync mode)
217
+ // ---------------------------------------------------------------------------
218
+ // 5. Update Cargo.lock (only in sync mode)
219
+ // ---------------------------------------------------------------------------
183
220
  if (!checkOnly && cargoVersion !== version) {
184
221
  let lockUpdated = false;
185
222
  try {
@@ -228,12 +265,12 @@ if (!checkOnly && cargoVersion !== version) {
228
265
  }
229
266
  }
230
267
 
231
- // 5. Report
268
+ // 6. Report
232
269
  if (checkOnly) {
233
270
  if (process.exitCode === 1) {
234
271
  console.error("\nVersion mismatch detected! Run 'node cli/scripts/sync-version.js' to fix.");
235
272
  } else {
236
- console.log(`\nAll versions in sync: ${version}`);
273
+ console.log(`\nAll versions in sync with cli/VERSION-CLI: ${version}`);
237
274
  }
238
275
  } else {
239
276
  console.log(`\nVersion sync complete: ${pkgName}@${version}`);