browserctl 0.4.0 → 0.6.0

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +97 -55
  4. data/bin/browserctl +117 -108
  5. data/bin/browserd +9 -3
  6. data/bin/setup +7 -3
  7. data/examples/cloudflare_hitl.rb +6 -6
  8. data/examples/smoke/params_file.rb +3 -2
  9. data/examples/smoke/store_fetch.rb +5 -5
  10. data/examples/test_automation_practices/checkboxes.rb +39 -0
  11. data/examples/test_automation_practices/dynamic_elements.rb +40 -0
  12. data/examples/test_automation_practices/key_press.rb +41 -0
  13. data/examples/test_automation_practices/login.rb +34 -0
  14. data/examples/test_automation_practices/login_negative.rb +28 -0
  15. data/examples/test_automation_practices/notifications.rb +57 -0
  16. data/examples/the_internet/add_remove_elements.rb +1 -1
  17. data/examples/the_internet/checkboxes.rb +1 -1
  18. data/examples/the_internet/dropdown.rb +1 -1
  19. data/examples/the_internet/dynamic_loading.rb +2 -2
  20. data/examples/the_internet/login.rb +1 -1
  21. data/lib/browserctl/client.rb +112 -28
  22. data/lib/browserctl/commands/cookie.rb +59 -0
  23. data/lib/browserctl/commands/daemon.rb +77 -0
  24. data/lib/browserctl/commands/page.rb +47 -0
  25. data/lib/browserctl/commands/record.rb +1 -1
  26. data/lib/browserctl/commands/screenshot.rb +2 -2
  27. data/lib/browserctl/commands/session.rb +69 -0
  28. data/lib/browserctl/commands/snapshot.rb +5 -5
  29. data/lib/browserctl/commands/storage.rb +67 -0
  30. data/lib/browserctl/commands/workflow.rb +64 -0
  31. data/lib/browserctl/constants.rb +20 -1
  32. data/lib/browserctl/detectors.rb +23 -0
  33. data/lib/browserctl/errors.rb +25 -0
  34. data/lib/browserctl/logger.rb +4 -4
  35. data/lib/browserctl/policy.rb +36 -0
  36. data/lib/browserctl/recording.rb +4 -4
  37. data/lib/browserctl/runner.rb +4 -4
  38. data/lib/browserctl/server/command_dispatcher.rb +49 -258
  39. data/lib/browserctl/server/handlers/cookies.rb +57 -0
  40. data/lib/browserctl/server/handlers/daemon_control.rb +29 -0
  41. data/lib/browserctl/server/handlers/devtools.rb +22 -0
  42. data/lib/browserctl/server/handlers/hitl.rb +31 -0
  43. data/lib/browserctl/server/handlers/navigation.rb +94 -0
  44. data/lib/browserctl/server/handlers/observation.rb +87 -0
  45. data/lib/browserctl/server/handlers/page_lifecycle.rb +36 -0
  46. data/lib/browserctl/server/handlers/session.rb +93 -0
  47. data/lib/browserctl/server/handlers/storage.rb +109 -0
  48. data/lib/browserctl/server.rb +4 -3
  49. data/lib/browserctl/session.rb +79 -0
  50. data/lib/browserctl/version.rb +1 -1
  51. data/lib/browserctl/workflow.rb +58 -17
  52. data/lib/browserctl.rb +12 -2
  53. metadata +43 -11
  54. data/lib/browserctl/commands/export_cookies.rb +0 -18
  55. data/lib/browserctl/commands/import_cookies.rb +0 -23
  56. data/lib/browserctl/commands/inspect.rb +0 -21
  57. data/lib/browserctl/commands/open_page.rb +0 -21
  58. data/lib/browserctl/commands/pause.rb +0 -22
  59. data/lib/browserctl/commands/status.rb +0 -30
  60. data/lib/browserctl/commands/watch.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa3b71160a355802bb8e1fd6667506af34dc354c97b60eb2e82e0b418c02dbe6
4
- data.tar.gz: 83da78edf2d59d84be5a6fd617e5b2388928c77b005d7735f466ce4aad19699b
3
+ metadata.gz: 37a7ccbe134ac1d42b514959c3db2ed17fd0229fdaed839842cf0af4a872d132
4
+ data.tar.gz: cea2a653eab69d4f544612122deb1bffe7827fca546b74c6f17f5fa8a16c6e8d
5
5
  SHA512:
6
- metadata.gz: 1ff6294fbc0284a342d9d2377077a02f004aec2acfeb3c1908039b09753d203e8a641d1dd2cd8ab2fd1fc607c42af3aa86537fb47361bb6820cf9a04fa8d6159
7
- data.tar.gz: d029df7a2d280172543cb52dcee92326c97464d3b8d750d2f718710d46db16402b8e1a235c83c5da229a2ae047a4ab92738c3d736f741a4964c1d324f7871c15
6
+ metadata.gz: 4bcf1b0d2200b774305555ec8d777d243556b4cb02c3aa7fc015392e6adec331613d17b020b0933212983914a8920beeab511b4dddadab7a2ee45f163897b84b
7
+ data.tar.gz: 551860c7cefd52148d764146e98083deb5c2cdea07e67fe458e33abf420c05a26e2783e763839e3bdd9011a0668a8fb1819f9284c97a47e746057afc3afeb6c3
data/CHANGELOG.md CHANGED
@@ -10,6 +10,51 @@ All notable changes to this project will be documented in this file.
10
10
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
11
11
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
12
 
13
+ ## [0.6.0](https://github.com/patrick204nqh/browserctl/compare/v0.5.0...v0.6.0) (2026-04-28)
14
+
15
+
16
+ ### Features
17
+
18
+ * add feedback capture guidelines and update .gitignore to include feedback directory ([03a436d](https://github.com/patrick204nqh/browserctl/commit/03a436d69d37826a6c24662b70d6852eac78aa54))
19
+ * **v0.6:** CLI redesign — noun-verb commands, storage/session, daemon auto-index ([#41](https://github.com/patrick204nqh/browserctl/issues/41)) ([b40040f](https://github.com/patrick204nqh/browserctl/commit/b40040f6fba7b78791e845e55af156be46bde3c3))
20
+ * **v0.6:** page_focus command, integration specs, and doc polish ([#42](https://github.com/patrick204nqh/browserctl/issues/42)) ([8cf006d](https://github.com/patrick204nqh/browserctl/commit/8cf006d0e3698bb0698c59d7af5b87f72a3cd154))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * correct initial checkbox state assertion to [false, true, false] ([#33](https://github.com/patrick204nqh/browserctl/issues/33)) ([5ab0fe4](https://github.com/patrick204nqh/browserctl/commit/5ab0fe4fbe5d5f244c795235f0531e0c970d75c7))
26
+ * dismiss on-load notifications before asserting counts ([#35](https://github.com/patrick204nqh/browserctl/issues/35)) ([b508ba6](https://github.com/patrick204nqh/browserctl/commit/b508ba6748d245526f6321a731e6a7bf4ad5cc86))
27
+ * fill replaces existing input value instead of appending ([#29](https://github.com/patrick204nqh/browserctl/issues/29)) ([c7abb48](https://github.com/patrick204nqh/browserctl/commit/c7abb48153e1c64fcd73e22a5bf376a98555f68c))
28
+ * notifications workflow — exclude container from counts, use evaluate for dismiss ([#34](https://github.com/patrick204nqh/browserctl/issues/34)) ([de43301](https://github.com/patrick204nqh/browserctl/commit/de43301942eb7a6335bc9fc7130c22ecf3dd8b38))
29
+ * README & CONTRIBUTING polish + Rakefile v0.6 CI fix ([#43](https://github.com/patrick204nqh/browserctl/issues/43)) ([422c614](https://github.com/patrick204nqh/browserctl/commit/422c6141035d47bb3fd4bc86f732029b44e35206))
30
+ * replace all remaining stale v0.5 CLI commands with v0.6 equivalents ([#45](https://github.com/patrick204nqh/browserctl/issues/45)) ([8d00923](https://github.com/patrick204nqh/browserctl/commit/8d0092380bbc7890761e6d8f5da7864358ce88f7))
31
+ * update demo assets installation and improve login tape requirements ([cf02e3a](https://github.com/patrick204nqh/browserctl/commit/cf02e3ad11fc99f1483904e396160d4345363e17))
32
+ * update login demo tape to use data-test selectors for input fields and buttons ([016fcda](https://github.com/patrick204nqh/browserctl/commit/016fcdaf77ce542d06efe898312063581fc3fffe))
33
+ * update stale `browserctl run` references to `browserctl workflow run` ([#44](https://github.com/patrick204nqh/browserctl/issues/44)) ([7f7005a](https://github.com/patrick204nqh/browserctl/commit/7f7005ac6fd6517c8751cf808378233343e9ace8))
34
+ * use baseline delta assertions instead of absolute notification counts ([#37](https://github.com/patrick204nqh/browserctl/issues/37)) ([33ac119](https://github.com/patrick204nqh/browserctl/commit/33ac119bbb43c0290fe03da06ca3f8545095f7a2))
35
+ * wait for notification-container before dismissing on-load toasts ([#36](https://github.com/patrick204nqh/browserctl/issues/36)) ([883cdf8](https://github.com/patrick204nqh/browserctl/commit/883cdf839bcef2d57bcb4764ff89862aa1969486))
36
+
37
+ ## [0.5.0](https://github.com/patrick204nqh/browserctl/compare/v0.4.0...v0.5.0) (2026-04-25)
38
+
39
+
40
+ ### Features
41
+
42
+ * add cookie export/import commands and refine interaction guidance ([1dc8b2c](https://github.com/patrick204nqh/browserctl/commit/1dc8b2c4c744a5f0930c28d3bcf93fd017c368b1))
43
+ * rename snapshot format 'ai' to 'elements' ([#22](https://github.com/patrick204nqh/browserctl/issues/22)) ([9fde6af](https://github.com/patrick204nqh/browserctl/commit/9fde6afb9e53b9556c84b4c24987777a5c266adf))
44
+ * v0.5 architecture & protocol lock ([#20](https://github.com/patrick204nqh/browserctl/issues/20)) ([1224f2f](https://github.com/patrick204nqh/browserctl/commit/1224f2fe2fb05119053831e7901b286fe93ad4fc))
45
+
46
+
47
+ ### Bug Fixes
48
+
49
+ * add rake as development dependency ([13902d8](https://github.com/patrick204nqh/browserctl/commit/13902d81fdb986c6ca754fcb16ce61ee850e27ce))
50
+ * improve browser GIF quality and fix terminal font rendering ([17afdb2](https://github.com/patrick204nqh/browserctl/commit/17afdb210916d8ebdabd237130da1fc534cdbd3e))
51
+ * open PR for demo assets instead of pushing directly to main ([f051316](https://github.com/patrick204nqh/browserctl/commit/f051316962d308419c08f60550cfb9910b148f14))
52
+ * quote filtergraph and add -update 1 for palette in browser GIF pipeline ([053d074](https://github.com/patrick204nqh/browserctl/commit/053d074b5fdd3e0ea6fc51589593d72d5f7fcd74))
53
+ * replace undefined REGISTRY constant with Browserctl.registry_snapshot ([d19a6bf](https://github.com/patrick204nqh/browserctl/commit/d19a6bf7ded8a5211f5b2a75511a9532f94e3845))
54
+ * update README for improved clarity and add Quick Start section ([881d914](https://github.com/patrick204nqh/browserctl/commit/881d914c2bf46625ee4ff5c1ca8ef21b462c533d))
55
+ * use app-slug output instead of gh api /app in assets workflow ([ae6a0f8](https://github.com/patrick204nqh/browserctl/commit/ae6a0f80ba2b66c76d681b1f258de5d72bc34c72))
56
+ * use CSS selectors for browser GIF and add full login flow frames ([717b86b](https://github.com/patrick204nqh/browserctl/commit/717b86befa9af6bab1823f984668ebdc29f2d7f8))
57
+
13
58
  ## [0.4.0](https://github.com/patrick204nqh/browserctl/compare/v0.3.1...v0.4.0) (2026-04-25)
14
59
 
15
60
 
data/README.md CHANGED
@@ -2,27 +2,96 @@
2
2
  <img src=".github/logo.svg" width="96" height="96" alt="browserctl logo"/>
3
3
  </p>
4
4
 
5
- # browserctl
5
+ <h1 align="center">browserctl</h1>
6
6
 
7
- [![CI](https://github.com/patrick204nqh/browserctl/actions/workflows/ci.yml/badge.svg)](https://github.com/patrick204nqh/browserctl/actions/workflows/ci.yml)
8
- [![Gem Version](https://badge.fury.io/rb/browserctl.svg)](https://badge.fury.io/rb/browserctl)
9
- [![Downloads](https://img.shields.io/gem/dt/browserctl)](https://rubygems.org/gems/browserctl)
7
+ <p align="center">
8
+ A browser daemon that keeps sessions alive between commands — for AI agents and iterative dev workflows.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/patrick204nqh/browserctl/actions/workflows/ci.yml"><img src="https://github.com/patrick204nqh/browserctl/actions/workflows/ci.yml/badge.svg" alt="CI"/></a>
13
+ <a href="https://badge.fury.io/rb/browserctl"><img src="https://badge.fury.io/rb/browserctl.svg" alt="Gem Version"/></a>
14
+ <a href="https://rubygems.org/gems/browserctl"><img src="https://img.shields.io/gem/dt/browserctl" alt="Downloads"/></a>
15
+ </p>
10
16
 
11
- A persistent browser automation daemon and CLI, purpose-built for AI agents and developer workflows.
17
+ ---
12
18
 
13
- Unlike tools that restart the browser on every script run, **browserctl keeps a named browser session alive** preserving cookies, localStorage, open tabs, and page state across discrete commands.
19
+ Every browser automation tool restarts the browser when your script ends. That means re-authenticating, re-navigating, re-loading state — on every run. browserctl doesn't restart. The session stays alive between commands, so you pick up exactly where you left off.
14
20
 
15
21
  ```bash
16
22
  browserd & # start the daemon (headless)
17
- browserctl open login --url https://example.com/login
18
- browserctl snap login # AI-friendly JSON snapshot with ref IDs
19
- browserctl fill login --ref e1 --value me@example.com # interact by ref, no selectors needed
20
- browserctl click login --ref e2
21
- browserctl shutdown
23
+ browserctl page open main --url https://example.com/login
24
+ browserctl snapshot main # AI-friendly JSON snapshot with ref IDs
25
+ browserctl fill main --ref e1 --value me@example.com # interact by ref, no selectors needed
26
+ browserctl click main --ref e2
27
+ browserctl daemon stop
28
+ ```
29
+
30
+ ---
31
+
32
+ ## See it in action
33
+
34
+ <table align="center"><tr>
35
+ <td align="center" width="50%">
36
+
37
+ **Terminal**<br/>
38
+ <sub>CLI commands, live output, session persistence proof</sub>
39
+
40
+ <img src="docs/assets/terminal.gif" alt="browserctl terminal demo"/>
41
+
42
+ </td>
43
+ <td align="center" width="50%">
44
+
45
+ **Browser**<br/>
46
+ <sub>What the browser sees as those commands run</sub>
47
+
48
+ <img src="docs/assets/browser_demo.gif" alt="browserctl browser demo"/>
49
+
50
+ </td>
51
+ </tr></table>
52
+
53
+ ---
54
+
55
+ ## Quick Start
56
+
57
+ ```bash
58
+ # 1. Install
59
+ gem install browserctl
60
+
61
+ # 2. Start the daemon
62
+ browserd &
63
+
64
+ # 3. Open a named page
65
+ browserctl page open main --url https://moatazeldebsy.github.io/test-automation-practices/#/auth
66
+
67
+ # 4. Snapshot — returns JSON with a ref ID per interactable element
68
+ browserctl snapshot main
69
+ # → [{"ref":"e1","tag":"input","attrs":{"data-test":"username-input"}}, {"ref":"e2",...}, {"ref":"e3","tag":"button","text":"Login",...}]
70
+
71
+ # 5. Interact using the ref IDs from the snapshot
72
+ browserctl fill main --ref e1 --value admin
73
+ browserctl fill main --ref e2 --value admin
74
+ browserctl click main --ref e3
75
+
76
+ # 6. Observe
77
+ browserctl url main
78
+ browserctl snapshot main --diff # only what changed
79
+
80
+ # 7. Done
81
+ browserctl daemon stop
22
82
  ```
23
83
 
24
- ![browserctl capturing a login flow](docs/screenshots/the_internet_login.png)
25
- <p align="center"><sub>Login flow captured with <code>browserctl shot</code></sub></p>
84
+ [Full Getting Started guide](docs/getting-started.md)
85
+
86
+ ---
87
+
88
+ ## Use cases
89
+
90
+ **AI coding agent authenticating into a staging environment** — the agent logs in once, the session persists, subsequent commands run inside the authenticated context without re-authenticating between steps.
91
+
92
+ **Developer reproducing a multi-step bug report** — navigate to the failure point once, then iterate on the fix with the browser already in the right state; no restarting from the home page each run.
93
+
94
+ **Automated smoke test that needs human sign-off** — the test runs until it hits something ambiguous, calls `browserctl pause`, lets a human inspect and act, then `browserctl resume` hands control back to the script with all state intact.
26
95
 
27
96
  ---
28
97
 
@@ -30,7 +99,7 @@ browserctl shutdown
30
99
 
31
100
  Most automation tools are stateless — every script spins up a fresh browser and tears it down. browserctl doesn't.
32
101
 
33
- | | browserctl | Playwright / Selenium |
102
+ | Capability | browserctl | Playwright / Selenium |
34
103
  |---|---|---|
35
104
  | Session persists across commands | ✓ | ✗ (per-script lifecycle) |
36
105
  | Named page handles | ✓ | ✗ |
@@ -46,15 +115,10 @@ Most automation tools are stateless — every script spins up a fresh browser an
46
115
 
47
116
  ---
48
117
 
49
- ## Requirements
50
-
51
- - Ruby >= 3.3
52
- - Chrome or Chromium on your `PATH`
53
-
54
- ---
55
-
56
118
  ## Installation
57
119
 
120
+ **Requirements:** Ruby >= 3.3 · Chrome or Chromium installed
121
+
58
122
  ```bash
59
123
  gem install browserctl
60
124
  ```
@@ -71,14 +135,14 @@ gem "browserctl"
71
135
 
72
136
  browserctl ships as a Claude Code plugin. Install it once and Claude automatically knows how to use the daemon, ref-based interaction, HITL patterns, and workflow authoring.
73
137
 
74
- **Install (interactive)**
138
+ **Interactive install**
75
139
 
76
140
  ```
77
141
  /plugin marketplace add patrick204nqh/browserctl
78
142
  /plugin install browserctl@browserctl
79
143
  ```
80
144
 
81
- **Install (project settings** — commit `.claude/settings.json` to share with your team)
145
+ **Project settings** — commit `.claude/settings.json` to share with your team:
82
146
 
83
147
  ```json
84
148
  {
@@ -93,36 +157,7 @@ browserctl ships as a Claude Code plugin. Install it once and Claude automatical
93
157
  }
94
158
  ```
95
159
 
96
- Once installed, Claude Code loads the `browserctl` skill automatically — no `/invoke` needed.
97
-
98
- ---
99
-
100
- ## Quick Start
101
-
102
- ```bash
103
- # 1. Start the daemon
104
- browserd &
105
-
106
- # 2. Open a named page
107
- browserctl open main --url https://the-internet.herokuapp.com/login
108
-
109
- # 3. Snapshot the page — get AI-friendly JSON with ref IDs
110
- browserctl snap main
111
-
112
- # 4. Interact using refs
113
- browserctl fill main --ref e1 --value tomsmith
114
- browserctl fill main --ref e2 --value SuperSecretPassword!
115
- browserctl click main --ref e3
116
-
117
- # 5. Observe
118
- browserctl url main
119
- browserctl snap main --diff # only what changed
120
-
121
- # 6. Done
122
- browserctl shutdown
123
- ```
124
-
125
- → [Full Getting Started guide](docs/getting-started.md)
160
+ Once installed, the `browserctl` skill loads automatically.
126
161
 
127
162
  ---
128
163
 
@@ -135,7 +170,7 @@ Start multiple named instances for agent isolation:
135
170
  ```bash
136
171
  browserd --name agent-a &
137
172
  browserd --name agent-b &
138
- browserctl --daemon agent-a open main --url https://app.example.com
173
+ browserctl --daemon agent-a page open main --url https://app.example.com
139
174
  ```
140
175
 
141
176
  The daemon shuts itself down after 30 minutes of inactivity.
@@ -162,12 +197,19 @@ The daemon shuts itself down after 30 minutes of inactivity.
162
197
  ```bash
163
198
  git clone https://github.com/patrick204nqh/browserctl
164
199
  cd browserctl
165
- bin/setup # install deps + check for Chrome
200
+ bin/setup # brew bundle (macOS) + bundle install + Chrome check
166
201
 
167
202
  bundle exec rspec # run tests
168
203
  bundle exec rubocop # lint
204
+
205
+ rake demo # full pipeline: screenshots + browser GIF + terminal GIF
206
+ rake demo:screenshots # smoke test screenshots only
207
+ rake demo:browser_gif # browser animation only (requires: ffmpeg)
208
+ rake demo:terminal # terminal GIF only (requires: vhs)
169
209
  ```
170
210
 
211
+ > Demo assets are regenerated automatically on every push to `main` that touches `demo/` or the login example.
212
+
171
213
  ---
172
214
 
173
215
  ## Contributing
data/bin/browserctl CHANGED
@@ -14,20 +14,19 @@ require "json"
14
14
  require "optimist"
15
15
  require "browserctl"
16
16
  require "browserctl/commands/cli_output"
17
- require "browserctl/commands/open_page"
18
17
  require "browserctl/commands/fill"
19
18
  require "browserctl/commands/click"
20
19
  require "browserctl/commands/snapshot"
21
20
  require "browserctl/commands/screenshot"
22
- require "browserctl/commands/watch"
23
21
  require "browserctl/commands/record"
24
- require "browserctl/commands/pause"
25
22
  require "browserctl/commands/resume"
26
23
  require "browserctl/commands/init"
27
- require "browserctl/commands/inspect"
28
- require "browserctl/commands/export_cookies"
29
- require "browserctl/commands/import_cookies"
30
- require "browserctl/commands/status"
24
+ require "browserctl/commands/page"
25
+ require "browserctl/commands/cookie"
26
+ require "browserctl/commands/storage"
27
+ require "browserctl/commands/session"
28
+ require "browserctl/commands/daemon"
29
+ require "browserctl/commands/workflow"
31
30
 
32
31
  def print_result(res)
33
32
  if res.is_a?(Hash) && res[:error]
@@ -37,59 +36,79 @@ def print_result(res)
37
36
  puts res.to_json
38
37
  end
39
38
 
40
- # rubocop:disable Metrics/MethodLength
41
39
  def usage
42
40
  puts <<~USAGE
43
41
  Usage: browserctl <command> [args]
44
42
 
45
43
  Setup:
46
- init Scaffold .browserctl/ in this project
47
-
48
- Browser commands (require browserd running):
49
- open <page> [--url URL] Open or focus a named page
50
- close <page> Close a named page
51
- pages List open pages
52
- goto <page> <url> Navigate a page
53
- fill <page> <selector> <value> Fill an input
54
- <page> --ref <ref> --value <value> Fill via snapshot ref
55
- click <page> <selector> Click an element
56
- <page> --ref <ref> Click via snapshot ref
57
- shot <page> [--out PATH] [--full] Take a screenshot
58
- snap <page> [--format ai|html] [--diff] Snapshot DOM (default: ai)
59
- url <page> Print current URL
60
- eval <page> <expression> Evaluate JS expression
61
- watch <page> <selector> [--timeout N] Wait for a selector to appear
62
- pause <page> Pause automation — browser stays live
63
- resume <page> Resume automation after manual action
64
- inspect <page> Open Chrome DevTools for a named page
65
- cookies <page> List all cookies as JSON
66
- set-cookie <page> <name> <value> <domain> Set a cookie (path defaults to /)
67
- clear-cookies <page> Clear all cookies for a page
68
- export-cookies <page> <path> Export cookies to a JSON file
69
- import-cookies <page> <path> Import cookies from a JSON file
70
-
71
- Recording commands:
72
- record start <name> Start recording browser commands
73
- record stop [--out PATH] Stop recording and save workflow
74
- record status Show active recording name
75
-
76
- Workflow commands:
77
- run <name|file> [--params file] [--key value ...] Run a workflow
78
- workflows List available workflows
79
- describe <name> Describe a workflow
80
-
81
- Daemon commands:
82
- ping Check if browserd is alive
83
- status Show daemon status, PID, and open pages
84
- shutdown Stop browserd
85
-
86
- Options:
87
- --daemon <name> Connect to a named daemon instance
88
- --version, -v Print version and exit
44
+ init
45
+
46
+ Page:
47
+ page open <name> [--url URL]
48
+ page close <name>
49
+ page list
50
+ page focus <name>
51
+
52
+ Interaction (page is always first arg after verb):
53
+ navigate <page> <url>
54
+ fill <page> <selector> <value>
55
+ <page> --ref <ref> --value <value>
56
+ click <page> <selector>
57
+ <page> --ref <ref>
58
+ snapshot <page> [--format elements|html] [--diff]
59
+ screenshot <page> [--out PATH] [--full]
60
+ evaluate <page> <expression>
61
+ url <page>
62
+ wait <page> <selector> [--timeout N]
63
+ pause <page> [--message MSG]
64
+ resume <page>
65
+ devtools <page>
66
+
67
+ Cookie:
68
+ cookie list <page>
69
+ cookie set <page> <name> <value> --domain DOMAIN [--path /]
70
+ cookie delete <page>
71
+ cookie export <page> <path>
72
+ cookie import <page> <path>
73
+
74
+ Storage:
75
+ storage get <page> <key> [--store local|session]
76
+ storage set <page> <key> <value> [--store local|session]
77
+ storage export <page> <path> [--store local|session|all]
78
+ storage import <page> <path>
79
+ storage delete <page> [--store local|session|all]
80
+
81
+ Session:
82
+ session save <name>
83
+ session load <name>
84
+ session list
85
+ session delete <name>
86
+ session export <name> <path>
87
+ session import <path>
88
+
89
+ Recording:
90
+ record start <name>
91
+ record stop [--out PATH]
92
+ record status
93
+
94
+ Workflow:
95
+ workflow run <name|file> [--params file] [--key value ...]
96
+ workflow list
97
+ workflow describe <name>
98
+
99
+ Daemon:
100
+ daemon start [--headed] [--name NAME]
101
+ daemon stop
102
+ daemon status
103
+ daemon ping
104
+ daemon list
105
+
106
+ Global options:
107
+ --daemon <name> Connect to named or auto-indexed daemon (d1, d2, work, ...)
108
+ --version, -v
89
109
  USAGE
90
110
  exit 0
91
111
  end
92
- # rubocop:enable Metrics/MethodLength
93
112
 
94
113
  daemon_idx = ARGV.index("--daemon")
95
114
  daemon_name = if daemon_idx
@@ -105,70 +124,60 @@ usage if cmd.nil? || %w[-h --help help].include?(cmd)
105
124
  runner = Browserctl::Runner.new
106
125
 
107
126
  case cmd
108
- when "run"
109
- name = args.shift or abort "usage: browserctl run <workflow_name|file.rb> [--key value ...]"
110
- if File.exist?(name)
111
- before = Browserctl::REGISTRY.keys.dup
112
- load File.expand_path(name)
113
- name = (Browserctl::REGISTRY.keys - before).first || File.basename(name, ".rb")
114
- end
115
- params_file_idx = args.index("--params")
116
- file_params = {}
117
- if params_file_idx
118
- params_path = args.delete_at(params_file_idx + 1)
119
- args.delete_at(params_file_idx)
120
- begin
121
- file_params = Browserctl::Runner.load_params_file(params_path)
122
- rescue StandardError => e
123
- abort "Error loading params file: #{e.message}"
124
- end
125
- end
126
- cli_params = {}
127
- args.each_slice(2) do |flag, val|
128
- key = flag.sub(/\A--/, "").to_sym
129
- cli_params[key] = val
130
- end
131
- params = file_params.merge(cli_params)
132
- success = runner.run_workflow(name, **params)
133
- exit(success ? 0 : 1)
134
-
135
- when "workflows"
136
- list = runner.list_workflows
137
- list.each { |w| puts "#{w[:name].ljust(24)} #{w[:desc]}" }
138
-
139
- when "describe"
140
- name = args.shift or abort "usage: browserctl describe <workflow_name>"
141
- puts JSON.pretty_generate(runner.describe_workflow(name))
142
-
143
- when "record" then Browserctl::Commands::Record.run(args)
144
- when "init" then Browserctl::Commands::Init.run(args)
127
+ when "workflow" then Browserctl::Commands::Workflow.run(runner, args)
128
+ when "record" then Browserctl::Commands::Record.run(args)
129
+ when "init" then Browserctl::Commands::Init.run(args)
145
130
 
146
131
  else
147
132
  client = Browserctl::Client.new(Browserctl.socket_path(daemon_name))
148
133
 
149
134
  case cmd
150
- when "open" then Browserctl::Commands::OpenPage.run(client, args)
151
- when "close" then print_result(client.close_page(args[0]))
152
- when "pages" then print_result(client.list_pages)
153
- when "goto" then print_result(client.goto(args[0], args[1]))
135
+ when "page" then Browserctl::Commands::Page.run(client, args)
136
+ when "cookie" then Browserctl::Commands::Cookie.run(client, args)
137
+ when "storage" then Browserctl::Commands::Storage.run(client, args)
138
+ when "session" then Browserctl::Commands::Session.run(client, args)
139
+ when "daemon" then Browserctl::Commands::Daemon.run(client, args)
140
+ when "navigate" then print_result(client.navigate(args[0], args[1]))
154
141
  when "fill" then Browserctl::Commands::Fill.run(client, args)
155
142
  when "click" then Browserctl::Commands::Click.run(client, args)
156
- when "shot" then Browserctl::Commands::Screenshot.run(client, args)
157
- when "snap" then Browserctl::Commands::Snapshot.run(client, args)
143
+ when "snapshot" then Browserctl::Commands::Snapshot.run(client, args)
144
+ when "screenshot" then Browserctl::Commands::Screenshot.run(client, args)
145
+ when "evaluate" then print_result(client.evaluate(args[0], args[1]))
158
146
  when "url" then print_result(client.url(args[0]))
159
- when "eval" then print_result(client.evaluate(args[0], args[1]))
160
- when "watch" then Browserctl::Commands::Watch.run(client, args)
161
- when "pause" then Browserctl::Commands::Pause.run(client, args)
162
- when "resume" then Browserctl::Commands::Resume.run(client, args)
163
- when "inspect" then Browserctl::Commands::Inspect.run(client, args)
164
- when "cookies" then print_result(client.cookies(args[0]))
165
- when "set-cookie" then print_result(client.set_cookie(args[0], args[1], args[2], args[3]))
166
- when "clear-cookies" then print_result(client.clear_cookies(args[0]))
167
- when "export-cookies" then Browserctl::Commands::ExportCookies.run(client, args)
168
- when "import-cookies" then Browserctl::Commands::ImportCookies.run(client, args)
169
- when "ping" then print_result(client.ping)
170
- when "status" then Browserctl::Commands::Status.run(client)
171
- when "shutdown" then print_result(client.shutdown)
147
+ when "wait"
148
+ opts = Optimist.options(args) do
149
+ opt :timeout, "Seconds to wait (default: 30)", default: 30.0, short: "-t"
150
+ end
151
+ name = args.shift
152
+ selector = args.shift
153
+ abort "usage: browserctl wait <page> <selector> [--timeout N]" unless name && selector
154
+ print_result(client.wait(name, selector, timeout: opts[:timeout]))
155
+ when "pause"
156
+ opts = Optimist.options(args) do
157
+ opt :message, "Message shown to human", type: :string, short: "-m"
158
+ end
159
+ name = args.shift or abort "usage: browserctl pause <page> [--message MSG]"
160
+ res = client.pause(name, message: opts[:message])
161
+ if res[:error]
162
+ warn "Error: #{res[:error]}"
163
+ exit 1
164
+ end
165
+ puts "Page '#{name}' paused. Browser is live — interact freely."
166
+ puts "(#{opts[:message]})" if opts[:message]
167
+ puts "When done: browserctl resume #{name}"
168
+ when "resume" then Browserctl::Commands::Resume.run(client, args)
169
+ when "devtools"
170
+ name = args.shift or abort "usage: browserctl devtools <page>"
171
+ res = client.devtools(name)
172
+ if res[:error]
173
+ warn "Error: #{res[:error]}"
174
+ exit 1
175
+ end
176
+ url = res[:devtools_url]
177
+ puts "Opening DevTools for '#{name}':"
178
+ puts " #{url}"
179
+ opener = RUBY_PLATFORM =~ /darwin/ ? "open" : "xdg-open"
180
+ system(opener, url)
172
181
  else
173
182
  abort "unknown command: #{cmd}\nRun 'browserctl --help' for usage."
174
183
  end
data/bin/browserd CHANGED
@@ -20,11 +20,17 @@ if opts[:name] && opts[:name] !~ /\A[a-zA-Z0-9_-]{1,64}\z/
20
20
  abort "Invalid daemon name #{opts[:name].inspect} — use only letters, digits, _ or -"
21
21
  end
22
22
 
23
- log_path = Browserctl.log_path(opts[:name])
23
+ assigned_name = opts[:name] || Browserctl.next_daemon_name
24
+ if assigned_name && !opts[:name]
25
+ warn "browserd: default slot taken — starting as '#{assigned_name}'"
26
+ warn " to connect: browserctl --daemon #{assigned_name} <command>"
27
+ end
28
+
29
+ log_path = Browserctl.log_path(assigned_name)
24
30
  warn "browserd starting — log: #{log_path}"
25
31
  Browserctl.logger = Browserctl.build_logger(opts[:log_level], log_path: log_path)
26
32
  Browserctl::Server.new(
27
33
  headless: !opts[:headed],
28
- socket_path: Browserctl.socket_path(opts[:name]),
29
- pid_path: Browserctl.pid_path(opts[:name])
34
+ socket_path: Browserctl.socket_path(assigned_name),
35
+ pid_path: Browserctl.pid_path(assigned_name)
30
36
  ).run
data/bin/setup CHANGED
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- echo "==> Installing dependencies..."
4
+ if [[ "$(uname)" == "Darwin" ]] && command -v brew &>/dev/null; then
5
+ echo "==> Installing Homebrew dependencies (Brewfile)..."
6
+ brew bundle --no-upgrade
7
+ fi
8
+
9
+ echo "==> Installing gem dependencies..."
5
10
  bundle install
6
11
 
7
12
  echo "==> Checking for Chrome/Chromium..."
8
13
  if ! command -v google-chrome &>/dev/null && ! command -v chromium-browser &>/dev/null && ! command -v chromium &>/dev/null; then
9
14
  echo "WARNING: Chrome/Chromium not found on PATH."
10
- echo " Install it before running browserctl."
11
- echo " macOS: brew install --cask google-chrome"
15
+ echo " macOS: brew bundle (includes google-chrome)"
12
16
  echo " Ubuntu: sudo apt-get install -y chromium-browser"
13
17
  fi
14
18
 
@@ -7,7 +7,7 @@
7
7
  # pauses automation so a human can solve it in the live browser, then resumes.
8
8
  #
9
9
  # Run:
10
- # browserctl run examples/cloudflare_hitl.rb --url https://example.com
10
+ # browserctl workflow run examples/cloudflare_hitl.rb --url https://example.com
11
11
  #
12
12
  # Note: modern Cloudflare often passes a real headed Chrome without challenge.
13
13
  # The pause/resume branch fires only when the challenge page is actually served
@@ -22,11 +22,11 @@ Browserctl.workflow "cloudflare_hitl" do
22
22
  param :selector, default: "body"
23
23
 
24
24
  step "open page" do
25
- client.open_page("main")
25
+ open_page(:main)
26
26
  end
27
27
 
28
28
  step "navigate to target URL" do
29
- res = client.goto("main", url)
29
+ res = client.navigate("main", url)
30
30
 
31
31
  if res[:challenge]
32
32
  $stdout.puts ""
@@ -54,12 +54,12 @@ Browserctl.workflow "cloudflare_hitl" do
54
54
  end
55
55
 
56
56
  step "wait for content and snapshot" do
57
- page(:main).wait_for(selector, timeout: 15)
58
- result = page(:main).snapshot(format: "ai")
57
+ page(:main).wait(selector, timeout: 15)
58
+ result = page(:main).snapshot(format: "elements")
59
59
  $stdout.puts " Snapshot: #{result[:snapshot]&.length || 0} elements captured"
60
60
  end
61
61
 
62
62
  step "close page" do
63
- client.close_page("main")
63
+ close_page(:main)
64
64
  end
65
65
  end