headless_browser_tool 0.1.1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/settings.json +21 -0
  3. data/.rubocop.yml +56 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CLAUDE.md +298 -0
  7. data/LICENSE.md +7 -0
  8. data/README.md +522 -0
  9. data/Rakefile +12 -0
  10. data/config.ru +8 -0
  11. data/exe/hbt +7 -0
  12. data/lib/headless_browser_tool/browser.rb +374 -0
  13. data/lib/headless_browser_tool/browser_adapter.rb +320 -0
  14. data/lib/headless_browser_tool/cli.rb +34 -0
  15. data/lib/headless_browser_tool/directory_setup.rb +25 -0
  16. data/lib/headless_browser_tool/logger.rb +31 -0
  17. data/lib/headless_browser_tool/server.rb +150 -0
  18. data/lib/headless_browser_tool/session_manager.rb +199 -0
  19. data/lib/headless_browser_tool/session_middleware.rb +158 -0
  20. data/lib/headless_browser_tool/session_persistence.rb +146 -0
  21. data/lib/headless_browser_tool/stdio_server.rb +73 -0
  22. data/lib/headless_browser_tool/strict_session_middleware.rb +88 -0
  23. data/lib/headless_browser_tool/tools/attach_file_tool.rb +40 -0
  24. data/lib/headless_browser_tool/tools/auto_narrate_tool.rb +155 -0
  25. data/lib/headless_browser_tool/tools/base_tool.rb +39 -0
  26. data/lib/headless_browser_tool/tools/check_tool.rb +35 -0
  27. data/lib/headless_browser_tool/tools/choose_tool.rb +56 -0
  28. data/lib/headless_browser_tool/tools/click_button_tool.rb +49 -0
  29. data/lib/headless_browser_tool/tools/click_link_tool.rb +48 -0
  30. data/lib/headless_browser_tool/tools/click_tool.rb +45 -0
  31. data/lib/headless_browser_tool/tools/close_window_tool.rb +31 -0
  32. data/lib/headless_browser_tool/tools/double_click_tool.rb +37 -0
  33. data/lib/headless_browser_tool/tools/drag_tool.rb +46 -0
  34. data/lib/headless_browser_tool/tools/evaluate_script_tool.rb +20 -0
  35. data/lib/headless_browser_tool/tools/execute_script_tool.rb +29 -0
  36. data/lib/headless_browser_tool/tools/fill_in_tool.rb +66 -0
  37. data/lib/headless_browser_tool/tools/find_all_tool.rb +42 -0
  38. data/lib/headless_browser_tool/tools/find_element_tool.rb +21 -0
  39. data/lib/headless_browser_tool/tools/find_elements_containing_text_tool.rb +259 -0
  40. data/lib/headless_browser_tool/tools/get_attribute_tool.rb +21 -0
  41. data/lib/headless_browser_tool/tools/get_current_path_tool.rb +16 -0
  42. data/lib/headless_browser_tool/tools/get_current_url_tool.rb +16 -0
  43. data/lib/headless_browser_tool/tools/get_narration_history_tool.rb +35 -0
  44. data/lib/headless_browser_tool/tools/get_page_context_tool.rb +188 -0
  45. data/lib/headless_browser_tool/tools/get_page_source_tool.rb +16 -0
  46. data/lib/headless_browser_tool/tools/get_page_title_tool.rb +16 -0
  47. data/lib/headless_browser_tool/tools/get_session_info_tool.rb +37 -0
  48. data/lib/headless_browser_tool/tools/get_text_tool.rb +20 -0
  49. data/lib/headless_browser_tool/tools/get_value_tool.rb +20 -0
  50. data/lib/headless_browser_tool/tools/get_window_handles_tool.rb +29 -0
  51. data/lib/headless_browser_tool/tools/go_back_tool.rb +29 -0
  52. data/lib/headless_browser_tool/tools/go_forward_tool.rb +29 -0
  53. data/lib/headless_browser_tool/tools/has_element_tool.rb +21 -0
  54. data/lib/headless_browser_tool/tools/has_text_tool.rb +21 -0
  55. data/lib/headless_browser_tool/tools/hover_tool.rb +38 -0
  56. data/lib/headless_browser_tool/tools/is_visible_tool.rb +20 -0
  57. data/lib/headless_browser_tool/tools/maximize_window_tool.rb +34 -0
  58. data/lib/headless_browser_tool/tools/open_new_window_tool.rb +25 -0
  59. data/lib/headless_browser_tool/tools/refresh_tool.rb +32 -0
  60. data/lib/headless_browser_tool/tools/resize_window_tool.rb +43 -0
  61. data/lib/headless_browser_tool/tools/right_click_tool.rb +37 -0
  62. data/lib/headless_browser_tool/tools/save_page_tool.rb +32 -0
  63. data/lib/headless_browser_tool/tools/screenshot_tool.rb +199 -0
  64. data/lib/headless_browser_tool/tools/search_page_tool.rb +224 -0
  65. data/lib/headless_browser_tool/tools/search_source_tool.rb +148 -0
  66. data/lib/headless_browser_tool/tools/select_tool.rb +44 -0
  67. data/lib/headless_browser_tool/tools/switch_to_window_tool.rb +30 -0
  68. data/lib/headless_browser_tool/tools/uncheck_tool.rb +35 -0
  69. data/lib/headless_browser_tool/tools/visit_tool.rb +27 -0
  70. data/lib/headless_browser_tool/tools/visual_diff_tool.rb +177 -0
  71. data/lib/headless_browser_tool/tools.rb +104 -0
  72. data/lib/headless_browser_tool/version.rb +5 -0
  73. data/lib/headless_browser_tool.rb +8 -0
  74. metadata +256 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8de5258869d3947a9922ab74f204fc3fc88c130004f89edcaf27890638dc17c9
4
+ data.tar.gz: a077e65a53fd7bd0c83db2d59608ad6d9f4a619757f7d05fe4157271d0ee41af
5
+ SHA512:
6
+ metadata.gz: 1b0f10bdc0f8f1e6884980c0aeffa01774d618e838606677d99c67f14764add4ad55ae4782aac7e603aa7a453085a8a3a286a2709d6f73cda2c737cf3195c2dd
7
+ data.tar.gz: 7828e19fa201427591c2db07f1cd64168998e347976dbf2b57aa5a616eaad97dbbb3e51baa6d79e475b141fd0b3f376fc45abc2fcfdc3a935598433d576412c4
@@ -0,0 +1,21 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash",
5
+ "Edit",
6
+ "Glob:",
7
+ "Grep",
8
+ "List",
9
+ "LS",
10
+ "Read",
11
+ "Write",
12
+ "Search",
13
+ "Write",
14
+ "WebSearch",
15
+ "Write",
16
+ "Read",
17
+ "WebFetch"
18
+ ],
19
+ "deny": []
20
+ }
21
+ }
data/.rubocop.yml ADDED
@@ -0,0 +1,56 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.4
3
+ NewCops: enable
4
+
5
+ plugins:
6
+ - rubocop-minitest
7
+ - rubocop-rake
8
+
9
+
10
+ Style/StringLiterals:
11
+ EnforcedStyle: double_quotes
12
+
13
+ Style/StringLiteralsInInterpolation:
14
+ EnforcedStyle: double_quotes
15
+
16
+ # Disable documentation for internal classes
17
+ Style/Documentation:
18
+ Enabled: false
19
+
20
+ # Allow slightly longer methods
21
+ Metrics/MethodLength:
22
+ Enabled: false
23
+
24
+ # Disable gemspec specific cops
25
+ Gemspec/RequireMFA:
26
+ Enabled: false
27
+
28
+ Gemspec/RequiredRubyVersion:
29
+ Enabled: false
30
+
31
+ Gemspec/DevelopmentDependencies:
32
+ Enabled: false
33
+
34
+ Naming/AccessorMethodName:
35
+ Enabled: false
36
+
37
+ Metrics/BlockLength:
38
+ Enabled: false
39
+
40
+ Metrics/ClassLength:
41
+ Enabled: false
42
+
43
+ Metrics/PerceivedComplexity:
44
+ Enabled: false
45
+
46
+ Metrics/CyclomaticComplexity:
47
+ Enabled: false
48
+
49
+ Metrics/AbcSize:
50
+ Enabled: false
51
+
52
+ Naming/PredicateName:
53
+ Enabled: false
54
+
55
+ Layout/LineLength:
56
+ Max: 150
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.4.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.1] - 2025-05-23
4
+
5
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,298 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Quick Start for Development
6
+
7
+ When making changes to this codebase:
8
+
9
+ 1. **Always run tests and linter**: `rake` before committing
10
+ 2. **Auto-fix linting issues**: `rake rubocop -A`
11
+ 3. **Test stdio mode**: `hbt stdio` (logs to `.hbt/logs/PID.log`)
12
+ 4. **Test HTTP mode**: `hbt start` (logs to stdout)
13
+ 5. **Test with session**: `HBT_SESSION_ID=test hbt stdio`
14
+
15
+ ## Development Commands
16
+
17
+ ### Essential Commands
18
+ - **Run tests**: `rake test` or simply `rake`
19
+ - **Run linter**: `rake rubocop`
20
+ - **Run tests and linter**: `rake` (default task)
21
+ - **Run a single test**: `ruby -Ilib:test test/test_headless_browser_tool.rb -n test_method_name`
22
+ - **Build gem**: `bundle exec rake build`
23
+ - **Install locally**: `bundle exec rake install`
24
+ - **Release to RubyGems**: `bundle exec rake release`
25
+
26
+ ### Development Setup
27
+ - **Install dependencies**: `bin/setup` or `bundle install`
28
+ - **Open console**: `bin/console` (loads gem in IRB)
29
+
30
+ ## Code Architecture
31
+
32
+ This is a Ruby gem that provides an MCP (Model Context Protocol) server for browser automation:
33
+
34
+ ### Core Components
35
+
36
+ - **lib/headless_browser_tool.rb**: Main module entry point
37
+ - **lib/headless_browser_tool/server.rb**: HTTP server with SSE support for MCP
38
+ - **lib/headless_browser_tool/stdio_server.rb**: Stdio server for direct MCP communication
39
+ - **lib/headless_browser_tool/browser.rb**: Browser wrapper around Capybara/Selenium
40
+ - **lib/headless_browser_tool/browser_adapter.rb**: Adapter for multi-session browser instances
41
+ - **lib/headless_browser_tool/cli.rb**: Command-line interface (Thor-based)
42
+
43
+ ### Session Management
44
+
45
+ - **lib/headless_browser_tool/session_manager.rb**: Manages multiple browser sessions
46
+ - **lib/headless_browser_tool/session_middleware.rb**: Rack middleware for session routing
47
+ - **lib/headless_browser_tool/strict_session_middleware.rb**: Enforces X-Session-ID requirement
48
+ - **lib/headless_browser_tool/session_persistence.rb**: Handles session save/restore
49
+
50
+ ### Tools System
51
+
52
+ - **lib/headless_browser_tool/tools/**: 40+ browser automation tools
53
+ - **lib/headless_browser_tool/tools/base_tool.rb**: Base class for all tools
54
+ - Tools are auto-discovered and registered with the MCP server
55
+
56
+ ### Support Modules
57
+
58
+ - **lib/headless_browser_tool/logger.rb**: Logging system (stdout for HTTP, file for stdio)
59
+ - **lib/headless_browser_tool/directory_setup.rb**: Creates and manages .hbt/ directory structure
60
+ - **lib/headless_browser_tool/version.rb**: Gem version constant
61
+
62
+ ### Testing
63
+
64
+ - **test/**: Minitest test suite
65
+ - **examples/**: Example scripts for testing functionality
66
+
67
+ ## Development Guidelines
68
+
69
+ ### General Principles
70
+
71
+ 1. **DRY (Don't Repeat Yourself)**: Extract common functionality into modules
72
+ - Example: `SessionPersistence` module for session save/restore
73
+ - Example: `DirectorySetup` module for directory management
74
+
75
+ 2. **Structured Responses**: All tools should return structured data, not strings
76
+ ```ruby
77
+ # Bad
78
+ "Clicked element: #{selector}"
79
+
80
+ # Good
81
+ {
82
+ selector: selector,
83
+ element: { tag_name: "button", text: "Submit" },
84
+ navigation: { from: old_url, to: new_url },
85
+ status: "clicked"
86
+ }
87
+ ```
88
+
89
+ 3. **Element Selectors**: When returning arrays of elements, include selectors
90
+ ```ruby
91
+ elements.map.with_index do |element, index|
92
+ {
93
+ selector: "#{base_selector}:nth-of-type(#{index + 1})",
94
+ tag_name: element.tag_name,
95
+ text: element.text
96
+ }
97
+ end
98
+ ```
99
+
100
+ ### Tool Development
101
+
102
+ 1. **Inherit from BaseTool**: All tools must inherit from `HeadlessBrowserTool::Tools::BaseTool`
103
+
104
+ 2. **Use FastMCP DSL**: Define tool metadata using the DSL
105
+ ```ruby
106
+ tool_name "my_tool"
107
+ description "What this tool does"
108
+
109
+ arguments do
110
+ required(:param).filled(:string).description("Parameter description")
111
+ optional(:flag).filled(:bool).description("Optional flag")
112
+ end
113
+ ```
114
+
115
+ 3. **Implement execute method**: This is where the tool logic goes
116
+ ```ruby
117
+ def execute(param:, flag: false)
118
+ # Tool implementation
119
+ # Return structured data, not strings
120
+ end
121
+ ```
122
+
123
+ 4. **Error Handling**: Let exceptions bubble up - BaseTool handles logging
124
+
125
+ ### Session Management
126
+
127
+ 1. **Multi-session mode**: Default for HTTP server, requires X-Session-ID header
128
+ 2. **Single-session mode**: For stdio and legacy compatibility
129
+ 3. **Session persistence**: Use `SessionPersistence` module for save/restore
130
+
131
+ ### Logging
132
+
133
+ 1. **HTTP mode**: Log to stdout using `HeadlessBrowserTool::Logger.log`
134
+ 2. **Stdio mode**: Log to `.hbt/logs/PID.log` to avoid protocol interference
135
+ 3. **Always log tool calls**: BaseTool automatically logs all tool executions
136
+
137
+ ### Testing
138
+
139
+ 1. **Run full test suite**: `rake` (runs tests and rubocop)
140
+ 2. **Fix linting issues**: `rake rubocop -A` for auto-corrections
141
+ 3. **Test new tools**: Add integration tests when adding new tools
142
+
143
+ ### Code Style
144
+
145
+ 1. **Follow RuboCop rules**: Run `rake rubocop` before committing
146
+ 2. **Use descriptive names**: Tools should have clear, action-oriented names
147
+ 3. **Document parameters**: Use the DSL's `.description()` method
148
+ 4. **Keep methods focused**: Each method should do one thing well
149
+
150
+ ### Common Patterns
151
+
152
+ 1. **Finding elements with fallback**:
153
+ ```ruby
154
+ element = begin
155
+ browser.find_button(text_or_selector)
156
+ rescue Capybara::ElementNotFound
157
+ browser.find(text_or_selector)
158
+ end
159
+ ```
160
+
161
+ 2. **Capturing navigation changes**:
162
+ ```ruby
163
+ url_before = browser.current_url
164
+ # ... perform action ...
165
+ navigated = browser.current_url != url_before
166
+ ```
167
+
168
+ 3. **Safe attribute access**:
169
+ ```ruby
170
+ {
171
+ id: element[:id],
172
+ class: element[:class]
173
+ }.compact # Remove nil values
174
+ ```
175
+
176
+ 4. **Generating unique selectors for elements**:
177
+ ```ruby
178
+ # For nth element in a collection
179
+ "#{base_selector}:nth-of-type(#{index + 1})"
180
+
181
+ # Prefer ID selectors when available
182
+ selector = if element[:id] && !element[:id].empty?
183
+ "##{element[:id]}"
184
+ else
185
+ "#{base_selector}:nth-of-type(#{index + 1})"
186
+ end
187
+ ```
188
+
189
+ 5. **Tool result structure**:
190
+ ```ruby
191
+ {
192
+ # Primary data
193
+ selector: selector,
194
+ value: new_value,
195
+
196
+ # State changes
197
+ was_checked: old_state,
198
+ is_checked: new_state,
199
+
200
+ # Navigation info (if applicable)
201
+ navigation: {
202
+ navigated: url_changed,
203
+ from: old_url,
204
+ to: new_url
205
+ },
206
+
207
+ # Always include status
208
+ status: "success"
209
+ }
210
+ ```
211
+
212
+ ### Directory Structure
213
+
214
+ The gem creates a `.hbt/` directory:
215
+ ```
216
+ .hbt/
217
+ ├── .gitignore # Contains "*" to ignore all contents
218
+ ├── screenshots/ # Screenshot storage
219
+ ├── sessions/ # Session persistence files (JSON)
220
+ └── logs/ # Process logs (stdio mode only)
221
+ ```
222
+
223
+ ### Adding New Tools
224
+
225
+ 1. Create a new file in `lib/headless_browser_tool/tools/`
226
+ 2. Follow the naming convention: `action_noun_tool.rb`
227
+ 3. Register in the ALL_TOOLS constant
228
+ 4. Return structured data with relevant metadata
229
+ 5. Include selectors for any returned elements
230
+ 6. Test with both HTTP and stdio modes
231
+
232
+ ### Debugging Tips
233
+
234
+ 1. **Enable request headers**: `hbt start --show-headers`
235
+ 2. **Run with visible browser**: `hbt start --no-headless`
236
+ 3. **Check session files**: Look in `.hbt/sessions/SESSION_ID.json`
237
+ 4. **Review logs**: Check `.hbt/logs/PID.log` for stdio mode
238
+ 5. **Test specific tools**: Use `examples/test_*.rb` scripts
239
+
240
+ ### MCP Protocol Considerations
241
+
242
+ 1. **Stdio mode**: Never write to stdout (it's for MCP protocol only)
243
+ 2. **Response format**: Tools automatically wrap returns in MCP response format
244
+ 3. **Error handling**: Exceptions become MCP error responses
245
+ 4. **Tool discovery**: Tools are auto-discovered from the tools/ directory
246
+
247
+ ### Best Practices for Tool Responses
248
+
249
+ 1. **Always return hashes, not strings** - Structured data is easier to parse
250
+ 2. **Include status field** - Usually "success", "failed", or action-specific
251
+ 3. **Return before/after state** - For actions that change state
252
+ 4. **Include selectors** - For elements that might be interacted with later
253
+ 5. **Keep responses focused** - Only include relevant data
254
+ 6. **Use consistent field names** - `selector`, `element`, `status`, etc.
255
+
256
+ ### Performance Considerations
257
+
258
+ 1. **Batch operations**: Use `map.with_index` for element collections
259
+ 2. **Limit text content**: Truncate long text fields (e.g., `.substring(0, 200)`)
260
+ 3. **Lazy loading**: Only fetch additional data when needed
261
+ 4. **Session cleanup**: Sessions auto-expire after 30 minutes of inactivity
262
+
263
+ ## Troubleshooting Common Issues
264
+
265
+ ### "No session ID provided" Error
266
+ - **Cause**: Multi-session mode requires X-Session-ID header
267
+ - **Fix**: Add `-H "X-Session-ID: your-session"` to curl commands
268
+ - **Or**: Use `--single-session` flag for shared session mode
269
+
270
+ ### Stdio Logs Not Appearing
271
+ - **Location**: Check `.hbt/logs/PID.log` (not stdout)
272
+ - **Ensure**: Logger is initialized with `mode: :stdio`
273
+ - **Note**: Logger.log must use @instance, not @log
274
+
275
+ ### Session Not Persisting
276
+ - **HTTP mode**: Use `--session-id` with `--single-session`
277
+ - **Stdio mode**: Set `HBT_SESSION_ID` environment variable
278
+ - **Check**: Look for session files in `.hbt/sessions/`
279
+
280
+ ### Tool Not Found
281
+ - **Check**: Tool class is in `lib/headless_browser_tool/tools/`
282
+ - **Verify**: Tool is added to `ALL_TOOLS` constant
283
+ - **Ensure**: Tool class follows naming convention (e.g., `ClickTool`)
284
+
285
+ ### Element Not Found
286
+ - **Use**: Tools with wait parameters (e.g., `has_element` with `wait: true`)
287
+ - **Try**: More specific selectors (ID > class > tag)
288
+ - **Debug**: Use `screenshot` tool to see current page state
289
+
290
+ ## Important Notes
291
+
292
+ - Ruby >= 3.1.0 is required
293
+ - The gem uses Minitest for testing (not RSpec)
294
+ - RuboCop is configured for code style enforcement
295
+ - Uses Capybara with Selenium WebDriver for browser automation
296
+ - Implements MCP (Model Context Protocol) for LLM integration
297
+ - Supports both HTTP+SSE and stdio communication modes
298
+ - Chrome/Chromium must be installed on the system
data/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025-present Paulo Arruda
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.