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.
- checksums.yaml +7 -0
- data/.claude/settings.json +21 -0
- data/.rubocop.yml +56 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +298 -0
- data/LICENSE.md +7 -0
- data/README.md +522 -0
- data/Rakefile +12 -0
- data/config.ru +8 -0
- data/exe/hbt +7 -0
- data/lib/headless_browser_tool/browser.rb +374 -0
- data/lib/headless_browser_tool/browser_adapter.rb +320 -0
- data/lib/headless_browser_tool/cli.rb +34 -0
- data/lib/headless_browser_tool/directory_setup.rb +25 -0
- data/lib/headless_browser_tool/logger.rb +31 -0
- data/lib/headless_browser_tool/server.rb +150 -0
- data/lib/headless_browser_tool/session_manager.rb +199 -0
- data/lib/headless_browser_tool/session_middleware.rb +158 -0
- data/lib/headless_browser_tool/session_persistence.rb +146 -0
- data/lib/headless_browser_tool/stdio_server.rb +73 -0
- data/lib/headless_browser_tool/strict_session_middleware.rb +88 -0
- data/lib/headless_browser_tool/tools/attach_file_tool.rb +40 -0
- data/lib/headless_browser_tool/tools/auto_narrate_tool.rb +155 -0
- data/lib/headless_browser_tool/tools/base_tool.rb +39 -0
- data/lib/headless_browser_tool/tools/check_tool.rb +35 -0
- data/lib/headless_browser_tool/tools/choose_tool.rb +56 -0
- data/lib/headless_browser_tool/tools/click_button_tool.rb +49 -0
- data/lib/headless_browser_tool/tools/click_link_tool.rb +48 -0
- data/lib/headless_browser_tool/tools/click_tool.rb +45 -0
- data/lib/headless_browser_tool/tools/close_window_tool.rb +31 -0
- data/lib/headless_browser_tool/tools/double_click_tool.rb +37 -0
- data/lib/headless_browser_tool/tools/drag_tool.rb +46 -0
- data/lib/headless_browser_tool/tools/evaluate_script_tool.rb +20 -0
- data/lib/headless_browser_tool/tools/execute_script_tool.rb +29 -0
- data/lib/headless_browser_tool/tools/fill_in_tool.rb +66 -0
- data/lib/headless_browser_tool/tools/find_all_tool.rb +42 -0
- data/lib/headless_browser_tool/tools/find_element_tool.rb +21 -0
- data/lib/headless_browser_tool/tools/find_elements_containing_text_tool.rb +259 -0
- data/lib/headless_browser_tool/tools/get_attribute_tool.rb +21 -0
- data/lib/headless_browser_tool/tools/get_current_path_tool.rb +16 -0
- data/lib/headless_browser_tool/tools/get_current_url_tool.rb +16 -0
- data/lib/headless_browser_tool/tools/get_narration_history_tool.rb +35 -0
- data/lib/headless_browser_tool/tools/get_page_context_tool.rb +188 -0
- data/lib/headless_browser_tool/tools/get_page_source_tool.rb +16 -0
- data/lib/headless_browser_tool/tools/get_page_title_tool.rb +16 -0
- data/lib/headless_browser_tool/tools/get_session_info_tool.rb +37 -0
- data/lib/headless_browser_tool/tools/get_text_tool.rb +20 -0
- data/lib/headless_browser_tool/tools/get_value_tool.rb +20 -0
- data/lib/headless_browser_tool/tools/get_window_handles_tool.rb +29 -0
- data/lib/headless_browser_tool/tools/go_back_tool.rb +29 -0
- data/lib/headless_browser_tool/tools/go_forward_tool.rb +29 -0
- data/lib/headless_browser_tool/tools/has_element_tool.rb +21 -0
- data/lib/headless_browser_tool/tools/has_text_tool.rb +21 -0
- data/lib/headless_browser_tool/tools/hover_tool.rb +38 -0
- data/lib/headless_browser_tool/tools/is_visible_tool.rb +20 -0
- data/lib/headless_browser_tool/tools/maximize_window_tool.rb +34 -0
- data/lib/headless_browser_tool/tools/open_new_window_tool.rb +25 -0
- data/lib/headless_browser_tool/tools/refresh_tool.rb +32 -0
- data/lib/headless_browser_tool/tools/resize_window_tool.rb +43 -0
- data/lib/headless_browser_tool/tools/right_click_tool.rb +37 -0
- data/lib/headless_browser_tool/tools/save_page_tool.rb +32 -0
- data/lib/headless_browser_tool/tools/screenshot_tool.rb +199 -0
- data/lib/headless_browser_tool/tools/search_page_tool.rb +224 -0
- data/lib/headless_browser_tool/tools/search_source_tool.rb +148 -0
- data/lib/headless_browser_tool/tools/select_tool.rb +44 -0
- data/lib/headless_browser_tool/tools/switch_to_window_tool.rb +30 -0
- data/lib/headless_browser_tool/tools/uncheck_tool.rb +35 -0
- data/lib/headless_browser_tool/tools/visit_tool.rb +27 -0
- data/lib/headless_browser_tool/tools/visual_diff_tool.rb +177 -0
- data/lib/headless_browser_tool/tools.rb +104 -0
- data/lib/headless_browser_tool/version.rb +5 -0
- data/lib/headless_browser_tool.rb +8 -0
- 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
|
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
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.
|