browser4-cli 0.1.26 → 0.1.27

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.
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.26",
3
+ "version": "0.1.27",
4
4
  "description": "Browser automation CLI for AI agents",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env pwsh
2
+ <#
3
+ .SYNOPSIS
4
+ Enumerates every browser4-cli command and subcommand, prints help
5
+ information, and saves output to categorized files.
6
+ .DESCRIPTION
7
+ This script discovers all browser4-cli commands (including hidden ones),
8
+ generates detailed help for each, and organizes the output into:
9
+ - A top-level help overview
10
+ - One file per command (with numbered prefixes for ordering)
11
+ - Prefix group overviews (swarm, agent, htmlsnapshot, crawl, snapshot)
12
+ - An INDEX.md for easy navigation
13
+
14
+ Output is saved to: <repo-root>/cli/help-output/
15
+ .PARAMETER Binary
16
+ Path to the browser4-cli binary. Defaults to the release build in the
17
+ workspace: cli/browser4-cli/target/release/browser4-cli.exe
18
+ .PARAMETER OutDir
19
+ Directory for help output. Defaults to cli/help-output/ under the repo root.
20
+ .EXAMPLE
21
+ .\enumerate-help.ps1
22
+ .\enumerate-help.ps1 -Binary "C:\tools\browser4-cli.exe" -OutDir ".\docs\cli-help"
23
+ #>
24
+
25
+ param(
26
+ [string]$Binary,
27
+ [string]$OutDir
28
+ )
29
+
30
+ $ErrorActionPreference = "Stop"
31
+
32
+ # ---- Resolve paths ----------------------------------------------------------
33
+ $RepoRoot = Resolve-Path "$PSScriptRoot\..\.."
34
+
35
+ if (-not $Binary) {
36
+ $Binary = Join-Path $RepoRoot "cli\browser4-cli\target\release\browser4-cli.exe"
37
+ }
38
+ if (-not $OutDir) {
39
+ $OutDir = Join-Path $RepoRoot "cli\help-output"
40
+ }
41
+
42
+ if (-not (Test-Path $Binary)) {
43
+ Write-Error "browser4-cli binary not found at: $Binary"
44
+ Write-Host "Build it first: cd cli/browser4-cli && cargo build --release"
45
+ exit 1
46
+ }
47
+
48
+ # Ensure output directory exists
49
+ $null = New-Item -ItemType Directory -Force -Path $OutDir
50
+
51
+ # Normalize binary path for reliable invocation
52
+ $Binary = Resolve-Path $Binary
53
+
54
+ Write-Host "browser4-cli binary : $Binary"
55
+ Write-Host "Output directory : $OutDir"
56
+ Write-Host ""
57
+
58
+ # ---- Helper: invoke the CLI and capture output ------------------------------
59
+ function Invoke-Cli {
60
+ param([string[]]$Arguments)
61
+ $psi = [System.Diagnostics.ProcessStartInfo]::new()
62
+ $psi.FileName = $Binary
63
+ $psi.Arguments = $Arguments -join ' '
64
+ $psi.RedirectStandardOutput = $true
65
+ $psi.RedirectStandardError = $true
66
+ $psi.UseShellExecute = $false
67
+ $psi.CreateNoWindow = $true
68
+ $psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
69
+ $psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
70
+
71
+ $proc = [System.Diagnostics.Process]::new()
72
+ $proc.StartInfo = $psi
73
+ $null = $proc.Start()
74
+ $stdout = $proc.StandardOutput.ReadToEnd()
75
+ $stderr = $proc.StandardError.ReadToEnd()
76
+ $proc.WaitForExit(10000) | Out-Null
77
+
78
+ # Combine: stdout first, then stderr (stderr carries errors / tips)
79
+ $result = $stdout
80
+ if ($stderr.Trim().Length -gt 0) {
81
+ if ($result.Length -gt 0) { $result += "`n" }
82
+ $result += $stderr
83
+ }
84
+ return $result.TrimEnd()
85
+ }
86
+
87
+ # ---- Command definitions ----------------------------------------------------
88
+
89
+ # Each entry: @{ Name = "<internal-name>"; HelpArgs = @("<arg1>", ...) }
90
+ #
91
+ # For simple commands (no spaced public form), use:
92
+ # HelpArgs = @("--help", "<internal-name>")
93
+ #
94
+ # For commands with a spaced public form, use:
95
+ # HelpArgs = @("<prefix>", "<sub>", "--help")
96
+ # Because `browser4-cli --help agent-run` is rejected; must use
97
+ # `browser4-cli agent run --help` instead.
98
+
99
+ $Commands = @(
100
+ # -- Browser sessions --
101
+ @{ Name = "open"; HelpArgs = @("--help", "open") },
102
+ @{ Name = "attach"; HelpArgs = @("--help", "attach") },
103
+ @{ Name = "close"; HelpArgs = @("--help", "close") },
104
+ @{ Name = "list"; HelpArgs = @("--help", "list") },
105
+ @{ Name = "close-all"; HelpArgs = @("--help", "close-all") },
106
+ @{ Name = "kill-all"; HelpArgs = @("--help", "kill-all") },
107
+ @{ Name = "stop"; HelpArgs = @("--help", "stop") },
108
+ @{ Name = "status"; HelpArgs = @("--help", "status") },
109
+ @{ Name = "doctor"; HelpArgs = @("--help", "doctor") },
110
+
111
+ # -- Navigation --
112
+ @{ Name = "goto"; HelpArgs = @("--help", "goto") },
113
+ @{ Name = "go-back"; HelpArgs = @("--help", "go-back") },
114
+ @{ Name = "go-forward"; HelpArgs = @("--help", "go-forward") },
115
+ @{ Name = "reload"; HelpArgs = @("--help", "reload") },
116
+
117
+ # -- Keyboard --
118
+ @{ Name = "press"; HelpArgs = @("--help", "press") },
119
+ @{ Name = "type"; HelpArgs = @("--help", "type") },
120
+ @{ Name = "keydown"; HelpArgs = @("--help", "keydown") },
121
+ @{ Name = "keyup"; HelpArgs = @("--help", "keyup") },
122
+ @{ Name = "fill"; HelpArgs = @("--help", "fill") },
123
+
124
+ # -- Mouse --
125
+ @{ Name = "click"; HelpArgs = @("--help", "click") },
126
+ @{ Name = "dblclick"; HelpArgs = @("--help", "dblclick") },
127
+ @{ Name = "hover"; HelpArgs = @("--help", "hover") },
128
+ @{ Name = "drag"; HelpArgs = @("--help", "drag") },
129
+ @{ Name = "mousemove"; HelpArgs = @("--help", "mousemove") },
130
+ @{ Name = "mousedown"; HelpArgs = @("--help", "mousedown") },
131
+ @{ Name = "mouseup"; HelpArgs = @("--help", "mouseup") },
132
+ @{ Name = "mousewheel"; HelpArgs = @("--help", "mousewheel") },
133
+ @{ Name = "scroll"; HelpArgs = @("--help", "scroll") },
134
+
135
+ # -- Core --
136
+ @{ Name = "snapshot"; HelpArgs = @("--help", "snapshot") },
137
+ @{ Name = "get"; HelpArgs = @("--help", "get") },
138
+ @{ Name = "eval"; HelpArgs = @("--help", "eval") },
139
+ @{ Name = "wait"; HelpArgs = @("--help", "wait") },
140
+ @{ Name = "select"; HelpArgs = @("--help", "select") },
141
+ @{ Name = "check"; HelpArgs = @("--help", "check") },
142
+ @{ Name = "uncheck"; HelpArgs = @("--help", "uncheck") },
143
+ @{ Name = "dialog-accept"; HelpArgs = @("--help", "dialog-accept") },
144
+ @{ Name = "dialog-dismiss"; HelpArgs = @("--help", "dialog-dismiss") },
145
+ @{ Name = "resize"; HelpArgs = @("--help", "resize") },
146
+ @{ Name = "delete-data"; HelpArgs = @("--help", "delete-data") },
147
+ @{ Name = "batch"; HelpArgs = @("--help", "batch") },
148
+ @{ Name = "generate-locator"; HelpArgs = @("--help", "generate-locator") },
149
+
150
+ # -- Export --
151
+ @{ Name = "screenshot"; HelpArgs = @("--help", "screenshot") },
152
+ @{ Name = "pdf"; HelpArgs = @("--help", "pdf") },
153
+
154
+ # -- Tabs --
155
+ @{ Name = "tab-list"; HelpArgs = @("--help", "tab-list") },
156
+ @{ Name = "tab-new"; HelpArgs = @("--help", "tab-new") },
157
+ @{ Name = "tab-close"; HelpArgs = @("--help", "tab-close") },
158
+ @{ Name = "tab-select"; HelpArgs = @("--help", "tab-select") },
159
+
160
+ # -- Storage --
161
+ @{ Name = "state-save"; HelpArgs = @("--help", "state-save") },
162
+ @{ Name = "state-load"; HelpArgs = @("--help", "state-load") },
163
+ @{ Name = "cookie-list"; HelpArgs = @("--help", "cookie-list") },
164
+ @{ Name = "cookie-get"; HelpArgs = @("--help", "cookie-get") },
165
+ @{ Name = "cookie-set"; HelpArgs = @("--help", "cookie-set") },
166
+ @{ Name = "cookie-delete"; HelpArgs = @("--help", "cookie-delete") },
167
+ @{ Name = "cookie-clear"; HelpArgs = @("--help", "cookie-clear") },
168
+ @{ Name = "localstorage-list"; HelpArgs = @("--help", "localstorage-list") },
169
+ @{ Name = "localstorage-get"; HelpArgs = @("--help", "localstorage-get") },
170
+ @{ Name = "localstorage-set"; HelpArgs = @("--help", "localstorage-set") },
171
+ @{ Name = "localstorage-delete"; HelpArgs = @("--help", "localstorage-delete") },
172
+ @{ Name = "localstorage-clear"; HelpArgs = @("--help", "localstorage-clear") },
173
+ @{ Name = "sessionstorage-list"; HelpArgs = @("--help", "sessionstorage-list") },
174
+ @{ Name = "sessionstorage-get"; HelpArgs = @("--help", "sessionstorage-get") },
175
+ @{ Name = "sessionstorage-set"; HelpArgs = @("--help", "sessionstorage-set") },
176
+ @{ Name = "sessionstorage-delete"; HelpArgs = @("--help", "sessionstorage-delete") },
177
+ @{ Name = "sessionstorage-clear"; HelpArgs = @("--help", "sessionstorage-clear") },
178
+
179
+ # -- Agent (hidden: agent-run, agent-status, agent-result) --
180
+ @{ Name = "extract"; HelpArgs = @("--help", "extract") },
181
+ # agent-list is a standalone command, not hidden
182
+ @{ Name = "agent-list"; HelpArgs = @("--help", "agent-list") },
183
+
184
+ # -- Snapshot subcommands (spaced-form: htmlsnapshot *, snapshot grep) --
185
+ @{ Name = "snapshot-grep"; HelpArgs = @("--help", "snapshot-grep") },
186
+
187
+ # -- DOM Snapshot (base command + spaced subcommands) --
188
+ @{ Name = "htmlsnapshot"; HelpArgs = @("--help", "htmlsnapshot") },
189
+
190
+ # -- Crawl --
191
+ @{ Name = "crawl"; HelpArgs = @("--help", "crawl") },
192
+
193
+ # -- Install / Admin --
194
+ @{ Name = "install"; HelpArgs = @("--help", "install") },
195
+ @{ Name = "upgrade"; HelpArgs = @("--help", "upgrade") },
196
+ @{ Name = "uninstall"; HelpArgs = @("--help", "uninstall") },
197
+ @{ Name = "loop"; HelpArgs = @("--help", "loop") }
198
+ )
199
+
200
+ # Commands with spaced public forms — the CLI rejects `--help <kebab-name>`
201
+ # for these, so we use `<prefix> <sub> --help` instead.
202
+ $SpacedCommands = @(
203
+ # Agent subcommands (hidden)
204
+ @{ Name = "agent-run"; HelpArgs = @("agent", "run", "--help") },
205
+ @{ Name = "agent-status"; HelpArgs = @("agent", "status", "--help") },
206
+ @{ Name = "agent-result"; HelpArgs = @("agent", "result", "--help") },
207
+
208
+ # Swarm subcommands
209
+ @{ Name = "swarm-create"; HelpArgs = @("swarm", "create", "--help") },
210
+ @{ Name = "swarm-submit"; HelpArgs = @("swarm", "submit", "--help") },
211
+ @{ Name = "swarm-query"; HelpArgs = @("swarm", "query", "--help") },
212
+ @{ Name = "swarm-status"; HelpArgs = @("swarm", "status", "--help") },
213
+ @{ Name = "swarm-result"; HelpArgs = @("swarm", "result", "--help") },
214
+ @{ Name = "swarm-list"; HelpArgs = @("swarm", "list", "--help") },
215
+
216
+ # DOM Snapshot subcommands
217
+ @{ Name = "htmlsnapshot-get"; HelpArgs = @("htmlsnapshot", "get", "--help") },
218
+ @{ Name = "htmlsnapshot-get-all"; HelpArgs = @("htmlsnapshot", "get", "all", "--help") },
219
+ @{ Name = "htmlsnapshot-query"; HelpArgs = @("htmlsnapshot", "query", "--help") },
220
+ @{ Name = "htmlsnapshot-export"; HelpArgs = @("htmlsnapshot", "export", "--help") },
221
+ @{ Name = "htmlsnapshot-summary"; HelpArgs = @("htmlsnapshot", "summary", "--help") },
222
+ @{ Name = "htmlsnapshot-grep"; HelpArgs = @("htmlsnapshot", "grep", "--help") },
223
+ @{ Name = "htmlsnapshot-inspect"; HelpArgs = @("htmlsnapshot", "inspect", "--help") },
224
+
225
+ # Crawl subcommand
226
+ @{ Name = "crawl-list"; HelpArgs = @("crawl", "list", "--help") }
227
+ )
228
+
229
+ # Hidden commands (not shown in top-level help, but included for completeness)
230
+ $HiddenCommands = @(
231
+ @{ Name = "upload"; HelpArgs = @("--help", "upload") },
232
+ @{ Name = "console"; HelpArgs = @("--help", "console") },
233
+ @{ Name = "summarize"; HelpArgs = @("--help", "summarize") }
234
+ )
235
+ # Note: agent-run, agent-status, agent-result are also hidden but already in $SpacedCommands
236
+
237
+ # Prefix groups to generate overview pages for
238
+ $PrefixGroups = @(
239
+ @{ Name = "swarm"; HelpArgs = @("--help", "swarm") },
240
+ @{ Name = "agent"; HelpArgs = @("--help", "agent") },
241
+ @{ Name = "htmlsnapshot"; HelpArgs = @("--help", "htmlsnapshot") },
242
+ @{ Name = "crawl"; HelpArgs = @("--help", "crawl") },
243
+ @{ Name = "snapshot"; HelpArgs = @("--help", "snapshot") }
244
+ )
245
+
246
+ # ---- Generate help files ----------------------------------------------------
247
+
248
+ $allFiles = @() # Track generated files for the index
249
+
250
+ # 1. Top-level help
251
+ Write-Host "[1/5] Generating top-level help..."
252
+ $topHelp = Invoke-Cli -Arguments @("--help")
253
+ $topFile = "00-top-level-help.txt"
254
+ $topHelp | Out-File -FilePath (Join-Path $OutDir $topFile) -Encoding utf8
255
+ $allFiles += @{ Path = $topFile; Label = "Top-level help (browser4-cli --help)" }
256
+ Write-Host " -> $topFile"
257
+
258
+ # 2. Per-command help (simple commands)
259
+ Write-Host "[2/5] Generating per-command help ($($Commands.Count) simple commands)..."
260
+ $idx = 0
261
+ foreach ($cmd in $Commands) {
262
+ $idx++
263
+ $padIdx = $idx.ToString("00")
264
+ $safeName = $cmd.Name -replace '[<>:"/\\|?*]', '_'
265
+ $fileName = "$padIdx-$safeName.txt"
266
+
267
+ Write-Host " [$idx/$($Commands.Count)] $($cmd.Name)"
268
+ $help = Invoke-Cli -Arguments $cmd.HelpArgs
269
+ $help | Out-File -FilePath (Join-Path $OutDir $fileName) -Encoding utf8
270
+ $allFiles += @{ Path = $fileName; Label = $cmd.Name }
271
+ }
272
+
273
+ # 3. Spaced-form command help
274
+ Write-Host "[3/5] Generating spaced-form command help ($($SpacedCommands.Count) commands)..."
275
+ $baseIdx = $idx
276
+ foreach ($cmd in $SpacedCommands) {
277
+ $idx++
278
+ $padIdx = $idx.ToString("00")
279
+ $safeName = $cmd.Name -replace '[<>:"/\\|?*]', '_'
280
+ $fileName = "$padIdx-$safeName.txt"
281
+
282
+ Write-Host " [$($idx - $baseIdx)/$($SpacedCommands.Count)] $($cmd.Name)"
283
+ $help = Invoke-Cli -Arguments $cmd.HelpArgs
284
+ $help | Out-File -FilePath (Join-Path $OutDir $fileName) -Encoding utf8
285
+ $allFiles += @{ Path = $fileName; Label = $cmd.Name }
286
+ }
287
+
288
+ # 4. Hidden commands
289
+ Write-Host "[4/5] Generating hidden-command help ($($HiddenCommands.Count) commands)..."
290
+ $baseIdx = $idx
291
+ foreach ($cmd in $HiddenCommands) {
292
+ $idx++
293
+ $padIdx = $idx.ToString("00")
294
+ $safeName = $cmd.Name -replace '[<>:"/\\|?*]', '_'
295
+ $fileName = "$padIdx-$safeName.txt"
296
+
297
+ Write-Host " [$($idx - $baseIdx)/$($HiddenCommands.Count)] $($cmd.Name) [hidden]"
298
+ $help = Invoke-Cli -Arguments $cmd.HelpArgs
299
+ $help | Out-File -FilePath (Join-Path $OutDir $fileName) -Encoding utf8
300
+ $allFiles += @{ Path = $fileName; Label = "$($cmd.Name) (hidden)" }
301
+ }
302
+
303
+ # 5. Prefix group overviews
304
+ Write-Host "[5/5] Generating prefix group overviews..."
305
+ foreach ($group in $PrefixGroups) {
306
+ $safeName = $group.Name
307
+ $fileName = "prefix-$safeName.txt"
308
+
309
+ Write-Host " prefix: $($group.Name)"
310
+ # For prefix groups, `--help <prefix>` prints subcommands
311
+ $help = Invoke-Cli -Arguments $group.HelpArgs
312
+ $help | Out-File -FilePath (Join-Path $OutDir $fileName) -Encoding utf8
313
+ $allFiles += @{ Path = $fileName; Label = "Prefix group: $($group.Name)" }
314
+ }
315
+
316
+ # ---- Generate INDEX.md ------------------------------------------------------
317
+ Write-Host ""
318
+ Write-Host "Generating INDEX.md..."
319
+
320
+ $indexLines = @(
321
+ "# browser4-cli Help Reference",
322
+ "",
323
+ "Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')",
324
+ "Binary: $Binary",
325
+ "",
326
+ "## Overview",
327
+ "",
328
+ "This directory contains the complete help output for every browser4-cli",
329
+ "command and subcommand.",
330
+ "",
331
+ "## Files",
332
+ ""
333
+ )
334
+
335
+ foreach ($entry in $allFiles) {
336
+ $indexLines += "- [$($entry.Label)]($($entry.Path))"
337
+ }
338
+
339
+ $totalCmdCount = $Commands.Count + $SpacedCommands.Count + $HiddenCommands.Count
340
+ $visibleCount = $totalCmdCount - 5 # 5 hidden: upload, console, summarize, agent-run, agent-result
341
+
342
+ $indexLines += (
343
+ "",
344
+ "## Quick Reference",
345
+ "",
346
+ "### Global help",
347
+ '```',
348
+ "browser4-cli --help",
349
+ '```',
350
+ "",
351
+ "### Per-command help (simple commands)",
352
+ '```',
353
+ "browser4-cli --help <command>",
354
+ '```',
355
+ "",
356
+ "### Per-command help (spaced subcommands)",
357
+ '```',
358
+ "browser4-cli <prefix> <sub> --help # e.g. browser4-cli swarm create --help",
359
+ "browser4-cli help <prefix> <sub> # equivalent",
360
+ '```',
361
+ "",
362
+ "### Prefix group overview",
363
+ '```',
364
+ "browser4-cli --help <prefix> # e.g. browser4-cli --help swarm",
365
+ '```',
366
+ "",
367
+ "## Command Count",
368
+ "",
369
+ "- **Total commands**: $totalCmdCount",
370
+ "- **Visible commands**: $visibleCount (5 hidden)",
371
+ "- **Hidden commands**: upload, console, summarize, agent-run, agent-status, agent-result",
372
+ ""
373
+ )
374
+
375
+ $indexContent = $indexLines -join "`n"
376
+ $indexPath = Join-Path $OutDir "INDEX.md"
377
+ $indexContent | Out-File -FilePath $indexPath -Encoding utf8
378
+
379
+ Write-Host "Done! All help files saved to: $OutDir"
380
+ Write-Host " Total files: $($allFiles.Count + 1) (including INDEX.md)"
381
+ Write-Host ""
382
+ Write-Host "Start here: $(Join-Path $OutDir 'INDEX.md')"
@@ -0,0 +1,257 @@
1
+ #!/bin/bash
2
+ # ---------------------------------------------------------------------------
3
+ # smoke-test-runtime-bundle.sh — Minimal CLI smoke test against a runtime bundle
4
+ # ---------------------------------------------------------------------------
5
+ # Usage:
6
+ # smoke-test-runtime-bundle.sh <cli-binary> <bundle-archive> [test-port] [timeout-secs]
7
+ #
8
+ # This script:
9
+ # 1. Extracts the platform runtime bundle (.tar.gz or .zip)
10
+ # 2. Sets up a local "installed" runtime directory (no network download)
11
+ # 3. Starts a Python HTTP server serving a minimal interactive page
12
+ # 4. Runs CLI commands: open → goto → type → click → close → kill-all
13
+ # 5. Reports pass/fail for each step
14
+ #
15
+ # Designed for CI (GitHub Actions) but also runs on developer machines.
16
+ # ---------------------------------------------------------------------------
17
+ set -euo pipefail
18
+
19
+ CLI_BINARY="${1:?Usage: $0 <cli-binary> <bundle-archive> [test-port] [timeout-secs]}"
20
+ BUNDLE_ARCHIVE="${2:?}"
21
+ TEST_PORT="${3:-0}" # 0 = OS picks a free port
22
+ TIMEOUT_SECS="${4:-300}"
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # helpers
26
+ # ---------------------------------------------------------------------------
27
+ RED='\033[0;31m'
28
+ GREEN='\033[0;32m'
29
+ YELLOW='\033[1;33m'
30
+ NC='\033[0m' # No Color
31
+
32
+ pass() { echo -e "${GREEN}[PASS]${NC} $*"; }
33
+ fail() { echo -e "${RED}[FAIL]${NC} $*"; }
34
+ info() { echo -e "${YELLOW}[INFO]${NC} $*"; }
35
+
36
+ cleanup() {
37
+ if [ -n "${HTTP_PID:-}" ] && kill -0 "$HTTP_PID" 2>/dev/null; then
38
+ kill "$HTTP_PID" 2>/dev/null || true
39
+ wait "$HTTP_PID" 2>/dev/null || true
40
+ fi
41
+ if [ -n "${TEMP_DIR:-}" ] && [ -d "$TEMP_DIR" ]; then
42
+ rm -rf "$TEMP_DIR"
43
+ fi
44
+ }
45
+ trap cleanup EXIT
46
+
47
+ # Resolve a free TCP port (port 0 not universally supported by Python http.server)
48
+ find_free_port() {
49
+ python3 -c '
50
+ import socket
51
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
52
+ s.bind(("127.0.0.1", 0))
53
+ print(s.getsockname()[1])
54
+ s.close()
55
+ ' 2>/dev/null || python -c '
56
+ import socket
57
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
58
+ s.bind(("127.0.0.1", 0))
59
+ print(s.getsockname()[1])
60
+ s.close()
61
+ '
62
+ }
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # validate inputs
66
+ # ---------------------------------------------------------------------------
67
+ if [ ! -f "$CLI_BINARY" ]; then
68
+ fail "CLI binary not found: $CLI_BINARY"
69
+ exit 1
70
+ fi
71
+ if [ ! -f "$BUNDLE_ARCHIVE" ]; then
72
+ fail "Bundle archive not found: $BUNDLE_ARCHIVE"
73
+ exit 1
74
+ fi
75
+
76
+ if [ "$TEST_PORT" -eq 0 ]; then
77
+ TEST_PORT=$(find_free_port)
78
+ fi
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # step 1: set up installed runtime directory
82
+ # ---------------------------------------------------------------------------
83
+ info "Setting up runtime directory..."
84
+
85
+ TEMP_DIR=$(mktemp -d)
86
+ RUNTIME_DIR="$TEMP_DIR/runtime-data"
87
+ VERSIONS_DIR="$RUNTIME_DIR/runtime" # daemon.rs: RUNTIME_VERSIONS_DIR_NAME
88
+ BUNDLE_TAG="v99.99.99-smoke"
89
+ BUNDLE_DIR="$VERSIONS_DIR/$BUNDLE_TAG" # versioned_install_dir(tag)
90
+ STATE_DIR="$TEMP_DIR/state"
91
+ WORKSPACE_DIR="$TEMP_DIR/workspace"
92
+ SERVER_LOG_DIR="$TEMP_DIR/server-logs"
93
+
94
+ mkdir -p "$BUNDLE_DIR" "$STATE_DIR" "$WORKSPACE_DIR" "$SERVER_LOG_DIR"
95
+ EXTRACT_DIR="$TEMP_DIR/extract"
96
+ mkdir -p "$EXTRACT_DIR"
97
+
98
+ # Extract archive (supports both .tar.gz and .zip)
99
+ case "$BUNDLE_ARCHIVE" in
100
+ *.tar.gz)
101
+ tar xzf "$BUNDLE_ARCHIVE" -C "$EXTRACT_DIR"
102
+ ;;
103
+ *.zip)
104
+ python3 -c "
105
+ import zipfile, sys
106
+ with zipfile.ZipFile('$BUNDLE_ARCHIVE', 'r') as z:
107
+ z.extractall('$EXTRACT_DIR')
108
+ " 2>/dev/null || unzip -q "$BUNDLE_ARCHIVE" -d "$EXTRACT_DIR"
109
+ ;;
110
+ *)
111
+ fail "Unsupported archive format: $BUNDLE_ARCHIVE (expected .tar.gz or .zip)"
112
+ exit 1
113
+ ;;
114
+ esac
115
+
116
+ # Find the bundle root (the directory containing runtime-bundle.json)
117
+ BUNDLE_ROOT=$(find "$EXTRACT_DIR" -name 'runtime-bundle.json' -maxdepth 5 | head -1 | xargs dirname 2>/dev/null || true)
118
+ if [ -z "$BUNDLE_ROOT" ] || [ ! -d "$BUNDLE_ROOT" ]; then
119
+ fail "Could not find runtime-bundle.json in extracted archive"
120
+ find "$EXTRACT_DIR" -maxdepth 3 -type d | while read -r d; do info " $d"; done
121
+ exit 1
122
+ fi
123
+ info "Bundle root: $BUNDLE_ROOT"
124
+
125
+ # Copy bundle contents into the versioned install directory
126
+ cp -r "$BUNDLE_ROOT"/* "$BUNDLE_DIR/" 2>/dev/null || cp -r "$BUNDLE_ROOT"/. "$BUNDLE_DIR/"
127
+
128
+ # Write minimal installation metadata (daemon.rs: BROWSER4_INSTALL_METADATA_FILE_NAME)
129
+ cat > "$BUNDLE_DIR/browser4-installation.json" <<META
130
+ {"tag":"$BUNDLE_TAG","asset_name":"smoke-test","download_url":"local","installed_at":"2026-01-01T00:00:00Z"}
131
+ META
132
+
133
+ # Write current.tag marker (daemon.rs: CURRENT_TAG_FILE_NAME)
134
+ echo "$BUNDLE_TAG" > "$VERSIONS_DIR/current.tag"
135
+
136
+ # On Unix, make the bundled java executable
137
+ if [ -f "$BUNDLE_DIR/runtime/bin/java" ]; then
138
+ chmod +x "$BUNDLE_DIR/runtime/bin/java" 2>/dev/null || true
139
+ fi
140
+
141
+ info "Runtime directory: $RUNTIME_DIR"
142
+ info "Bundle version dir: $BUNDLE_DIR"
143
+
144
+ # ---------------------------------------------------------------------------
145
+ # step 2: start test page server
146
+ # ---------------------------------------------------------------------------
147
+ info "Starting test HTTP server on port $TEST_PORT..."
148
+
149
+ TEST_PAGE="$TEMP_DIR/test.html"
150
+ cat > "$TEST_PAGE" <<'HTML'
151
+ <!DOCTYPE html>
152
+ <html><head><meta charset="utf-8"><title>Smoke Test</title></head><body>
153
+ <h1>Browser4 Smoke Test</h1>
154
+ <input id="input1" type="text" value="">
155
+ <button id="btn1" onclick="document.getElementById('input1').value='clicked'">Click Me</button>
156
+ </body></html>
157
+ HTML
158
+
159
+ python3 -m http.server "$TEST_PORT" --bind 127.0.0.1 > /dev/null 2>&1 &
160
+ HTTP_PID=$!
161
+ TEST_URL="http://127.0.0.1:$TEST_PORT/test.html"
162
+
163
+ # Wait for HTTP server to be ready
164
+ for i in $(seq 1 10); do
165
+ if curl -s -o /dev/null "$TEST_URL" 2>/dev/null; then
166
+ break
167
+ fi
168
+ sleep 0.5
169
+ done
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # step 3: determine server URL (let CLI auto-start the server)
173
+ # ---------------------------------------------------------------------------
174
+ # The CLI auto-starts the Browser4 server on the first command that needs it.
175
+ # We DON'T pass --server so the CLI manages the server lifecycle itself.
176
+ # This tests the full "CLI starts jlink JRE server" code path.
177
+
178
+ SERVER_PORT=$(find_free_port)
179
+ SERVER_URL="http://127.0.0.1:$SERVER_PORT"
180
+
181
+ run_cli() {
182
+ # Set env vars that the CLI checks:
183
+ # BROWSER4_RUNTIME_DIR → where the installed runtime lives
184
+ # BROWSER4_CLI_STATE_DIR → where CLI session state is persisted
185
+ # BROWSER4_SERVER_LOG_DIR → where server startup logs go
186
+ BROWSER4_CLI_STATE_DIR="$STATE_DIR" \
187
+ BROWSER4_RUNTIME_DIR="$RUNTIME_DIR" \
188
+ BROWSER4_SERVER_LOG_DIR="$SERVER_LOG_DIR" \
189
+ "$CLI_BINARY" --server "$SERVER_URL" "$@"
190
+ }
191
+
192
+ FAILED_STEPS=0
193
+ TOTAL_STEPS=0
194
+
195
+ check_step() {
196
+ local label="$1"
197
+ shift
198
+ TOTAL_STEPS=$((TOTAL_STEPS + 1))
199
+ echo ""
200
+ info "--- $label ---"
201
+ if run_cli "$@" 2>&1; then
202
+ pass "$label"
203
+ else
204
+ fail "$label"
205
+ FAILED_STEPS=$((FAILED_STEPS + 1))
206
+ # On first failure, dump the server log if available
207
+ if [ "$FAILED_STEPS" -eq 1 ]; then
208
+ if [ -d "$SERVER_LOG_DIR" ] && [ -n "$(ls -A "$SERVER_LOG_DIR" 2>/dev/null)" ]; then
209
+ info "--- server log (last 80 lines) ---"
210
+ find "$SERVER_LOG_DIR" -type f -name '*.log' -exec tail -80 {} \; 2>/dev/null || true
211
+ fi
212
+ # Also check the temp dir for server logs (default location)
213
+ local default_log_dir="${TMPDIR:-/tmp}/browser4/browser4-cli/tmp/cli"
214
+ if [ -d "$default_log_dir" ] && [ -n "$(ls -A "$default_log_dir" 2>/dev/null)" ]; then
215
+ info "--- default server log (last 80 lines) ---"
216
+ find "$default_log_dir" -type f -name '*.log' -exec tail -80 {} \; 2>/dev/null || true
217
+ fi
218
+ fi
219
+ fi
220
+ }
221
+
222
+ # ---------------------------------------------------------------------------
223
+ # step 4: run CLI commands
224
+ # ---------------------------------------------------------------------------
225
+
226
+ # 4a. open — starts the server, opens a browser session, navigates to the page
227
+ check_step "open" open "$TEST_URL" --profile-mode SEQUENTIAL --interact-level FASTEST
228
+
229
+ # 4b. goto — navigate to the page (should be a no-op or refresh)
230
+ check_step "goto" goto "$TEST_URL"
231
+
232
+ # 4c. type — fill the input field
233
+ check_step "type" type "hello-smoke" "#input1"
234
+
235
+ # 4d. click — click the button
236
+ check_step "click" click "#btn1"
237
+
238
+ # 4e. close — close the active session
239
+ check_step "close" close
240
+
241
+ # 4f. kill-all — stop the server and clean up
242
+ check_step "kill-all" kill-all
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # step 5: report
246
+ # ---------------------------------------------------------------------------
247
+ echo ""
248
+ echo "========================================"
249
+ if [ "$FAILED_STEPS" -eq 0 ]; then
250
+ pass "SMOKE TEST PASSED ($TOTAL_STEPS/$TOTAL_STEPS steps OK)"
251
+ exit 0
252
+ else
253
+ fail "SMOKE TEST FAILED ($FAILED_STEPS/$TOTAL_STEPS steps failed)"
254
+ info "Server logs at: $SERVER_LOG_DIR"
255
+ info "Temp directory: $TEMP_DIR"
256
+ exit 1
257
+ fi