browser4-cli 0.1.13 → 0.1.14

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.14",
4
4
  "description": "Browser automation CLI for AI agents",
5
5
  "type": "module",
6
6
  "files": [
@@ -216,6 +216,97 @@ function Invoke-Download {
216
216
  }
217
217
  }
218
218
 
219
+ # ──────────────────────────────────────────────
220
+ # Symlinks
221
+ # ──────────────────────────────────────────────
222
+
223
+ function New-PlatformLink {
224
+ param([string]$LinkPath, [string]$TargetName, [string]$DisplayName)
225
+
226
+ # Try symbolic link first (works on Unix; on Windows needs Admin or Developer Mode)
227
+ try {
228
+ New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetName -Force -ErrorAction Stop | Out-Null
229
+ Write-Check "Created symlink: $DisplayName -> $TargetName"
230
+ return $true
231
+ } catch {
232
+ # Symbolic link failed — try hard link on Windows
233
+ }
234
+
235
+ # Try hard link (Windows, same volume)
236
+ if ($script:IsWin) {
237
+ try {
238
+ $targetPath = Join-Path (Split-Path $LinkPath -Parent) $TargetName
239
+ New-Item -ItemType HardLink -Path $LinkPath -Target $targetPath -Force -ErrorAction Stop | Out-Null
240
+ Write-Check "Created hard link: $DisplayName -> $TargetName"
241
+ return $true
242
+ } catch {
243
+ # Hard link also failed — create a .cmd wrapper as last resort
244
+ }
245
+
246
+ # Last resort: .cmd wrapper that forwards all arguments
247
+ try {
248
+ $wrapperContent = '@"%~dp0' + $TargetName + '" %*'
249
+ Set-Content -Path ($LinkPath -replace '\.exe$', '.cmd') -Value $wrapperContent -Force -ErrorAction Stop
250
+ Write-Check "Created wrapper: " + (Split-Path ($LinkPath -replace '\.exe$', '.cmd') -Leaf) + " -> $TargetName"
251
+ return $true
252
+ } catch {
253
+ Write-WarnMsg "Could not create link for $DisplayName (may need admin privileges)"
254
+ return $false
255
+ }
256
+ }
257
+
258
+ Write-WarnMsg "Could not create link for $DisplayName"
259
+ return $false
260
+ }
261
+
262
+ function New-Symlinks {
263
+ param([string]$BinaryName, [string]$InstallDir, [string]$PlatformKey)
264
+
265
+ $ext = if ($PlatformKey.StartsWith("win32")) { ".exe" } else { "" }
266
+
267
+ # 1) Always: browser4-cli -> browser4-cli-<platform>
268
+ $linkName = "browser4-cli$ext"
269
+ $linkPath = Join-Path $InstallDir $linkName
270
+
271
+ if ($DryRun) {
272
+ Write-Step "[DRY-RUN] Would create link: $linkName -> $BinaryName"
273
+ } else {
274
+ New-PlatformLink -LinkPath $linkPath -TargetName $BinaryName -DisplayName $linkName
275
+ }
276
+
277
+ # 2) Only if no conflict: b4 -> browser4-cli-<platform>
278
+ $shortName = "b4$ext"
279
+ $shortPath = Join-Path $InstallDir $shortName
280
+
281
+ # Check if b4 is already on PATH (conflict with another tool)
282
+ $existingCmd = Get-Command b4 -ErrorAction SilentlyContinue
283
+ if ($existingCmd) {
284
+ Write-WarnMsg "Skipping short link '$shortName': 'b4' already found on PATH ($($existingCmd.Source))"
285
+ return
286
+ }
287
+
288
+ # Check if b4 already exists in the install directory
289
+ if (Test-Path $shortPath) {
290
+ Write-WarnMsg "Skipping short link '$shortName': already exists in $InstallDir"
291
+ return
292
+ }
293
+
294
+ # Also check b4.cmd if on Windows (wrapper fallback)
295
+ if ($script:IsWin) {
296
+ $shortCmdPath = Join-Path $InstallDir "b4.cmd"
297
+ if (Test-Path $shortCmdPath) {
298
+ Write-WarnMsg "Skipping short link '$shortName': 'b4.cmd' already exists in $InstallDir"
299
+ return
300
+ }
301
+ }
302
+
303
+ if ($DryRun) {
304
+ Write-Step "[DRY-RUN] Would create link: $shortName -> $BinaryName"
305
+ } else {
306
+ New-PlatformLink -LinkPath $shortPath -TargetName $BinaryName -DisplayName $shortName
307
+ }
308
+ }
309
+
219
310
  # ──────────────────────────────────────────────
220
311
  # PATH management
221
312
  # ──────────────────────────────────────────────
@@ -330,6 +421,10 @@ Please check:
330
421
  }
331
422
  }
332
423
 
424
+ # Create symlinks (browser4-cli -> platform binary, b4 if no conflict)
425
+ Write-Summary ""
426
+ New-Symlinks -BinaryName $binaryName -InstallDir $installDir -PlatformKey $platformKey
427
+
333
428
  # Add to PATH
334
429
  if ($AddToPath -and $script:IsWin) {
335
430
  Write-Summary ""
@@ -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}`);