crucible 0.1.2

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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +102 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +21 -0
  6. data/README.md +366 -0
  7. data/Rakefile +23 -0
  8. data/TESTING.md +319 -0
  9. data/config.sample.yml +48 -0
  10. data/crucible.gemspec +48 -0
  11. data/exe/crucible +122 -0
  12. data/lib/crucible/configuration.rb +212 -0
  13. data/lib/crucible/server.rb +123 -0
  14. data/lib/crucible/session_manager.rb +209 -0
  15. data/lib/crucible/stealth/evasions/chrome_app.js +75 -0
  16. data/lib/crucible/stealth/evasions/chrome_csi.js +33 -0
  17. data/lib/crucible/stealth/evasions/chrome_load_times.js +44 -0
  18. data/lib/crucible/stealth/evasions/chrome_runtime.js +190 -0
  19. data/lib/crucible/stealth/evasions/iframe_content_window.js +101 -0
  20. data/lib/crucible/stealth/evasions/media_codecs.js +65 -0
  21. data/lib/crucible/stealth/evasions/navigator_hardware_concurrency.js +18 -0
  22. data/lib/crucible/stealth/evasions/navigator_languages.js +18 -0
  23. data/lib/crucible/stealth/evasions/navigator_permissions.js +53 -0
  24. data/lib/crucible/stealth/evasions/navigator_plugins.js +261 -0
  25. data/lib/crucible/stealth/evasions/navigator_vendor.js +18 -0
  26. data/lib/crucible/stealth/evasions/navigator_webdriver.js +16 -0
  27. data/lib/crucible/stealth/evasions/webgl_vendor.js +43 -0
  28. data/lib/crucible/stealth/evasions/window_outerdimensions.js +18 -0
  29. data/lib/crucible/stealth/utils.js +266 -0
  30. data/lib/crucible/stealth.rb +213 -0
  31. data/lib/crucible/tools/cookies.rb +206 -0
  32. data/lib/crucible/tools/downloads.rb +273 -0
  33. data/lib/crucible/tools/extraction.rb +335 -0
  34. data/lib/crucible/tools/helpers.rb +46 -0
  35. data/lib/crucible/tools/interaction.rb +355 -0
  36. data/lib/crucible/tools/navigation.rb +181 -0
  37. data/lib/crucible/tools/sessions.rb +85 -0
  38. data/lib/crucible/tools/stealth.rb +167 -0
  39. data/lib/crucible/tools.rb +42 -0
  40. data/lib/crucible/version.rb +5 -0
  41. data/lib/crucible.rb +60 -0
  42. metadata +201 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e2c04da14bee7961c1ced708e2328f25a0b35073f5328738eb18b7837a743ce3
4
+ data.tar.gz: ad94610d74d10ae74132690247b6b7801a622323d5b45c256aefd380b03328c2
5
+ SHA512:
6
+ metadata.gz: 84058ca5a1bdec6bb411eff6c36335a254dcabc10055ae707d686a7dd2f179e9c165dd49a4c80c2ce22d0d2c18f38d6e8484d446e22f3b8b0fb84bb55cdd19b5
7
+ data.tar.gz: 9f5fcc4f0d4fd50b343cd3fc1be9be2e523620fb92324c11c29675bc4986b8fe039318c69109862ff754ce475cc13de3cb37a711d4a1a41c3388df793f33b5fe
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,102 @@
1
+ # RuboCop configuration for Crucible
2
+
3
+ plugins:
4
+ - rubocop-rake
5
+ - rubocop-rspec
6
+
7
+ AllCops:
8
+ NewCops: enable
9
+ TargetRubyVersion: 3.2
10
+ Exclude:
11
+ - 'vendor/**/*'
12
+ - 'tmp/**/*'
13
+
14
+ # Gemspec - development dependencies in gemspec is acceptable
15
+ Gemspec/DevelopmentDependencies:
16
+ Enabled: false
17
+
18
+ # Metrics - tool methods are inherently verbose due to MCP schema definitions
19
+ Metrics/BlockLength:
20
+ Exclude:
21
+ - 'spec/**/*'
22
+ - '*.gemspec'
23
+ - 'exe/*'
24
+ - 'lib/crucible/tools/**/*'
25
+ Max: 50
26
+
27
+ Metrics/ClassLength:
28
+ Max: 200
29
+ Exclude:
30
+ - 'lib/crucible/tools/**/*'
31
+
32
+ Metrics/ModuleLength:
33
+ Max: 200
34
+ Exclude:
35
+ - 'lib/crucible/tools/**/*'
36
+
37
+ Metrics/MethodLength:
38
+ Max: 25
39
+ Exclude:
40
+ - 'lib/crucible/tools/**/*'
41
+
42
+ Metrics/AbcSize:
43
+ Max: 35
44
+ Exclude:
45
+ - 'lib/crucible/tools/**/*'
46
+ - 'lib/crucible/configuration.rb'
47
+
48
+ Metrics/CyclomaticComplexity:
49
+ Max: 15
50
+ Exclude:
51
+ - 'lib/crucible/configuration.rb'
52
+ - 'lib/crucible/tools/**/*'
53
+
54
+ Metrics/PerceivedComplexity:
55
+ Max: 15
56
+ Exclude:
57
+ - 'lib/crucible/configuration.rb'
58
+ - 'lib/crucible/tools/**/*'
59
+
60
+ Metrics/ParameterLists:
61
+ Max: 8
62
+
63
+ # Naming - these aren't predicate methods or accessor methods
64
+ Naming/PredicateMethod:
65
+ Exclude:
66
+ - 'lib/crucible/configuration.rb'
67
+ - 'lib/crucible/session_manager.rb'
68
+
69
+ Naming/AccessorMethodName:
70
+ Exclude:
71
+ - 'lib/crucible/tools/**/*'
72
+
73
+ # Lint - duplicate rescue branches are intentional for different error types
74
+ Lint/DuplicateBranch:
75
+ Exclude:
76
+ - 'lib/crucible/tools/**/*'
77
+
78
+ # RSpec - tests need flexibility
79
+ RSpec/MultipleExpectations:
80
+ Max: 10
81
+
82
+ RSpec/ExampleLength:
83
+ Max: 70
84
+ Exclude:
85
+ - 'spec/e2e/**/*'
86
+
87
+ RSpec/NestedGroups:
88
+ Max: 4
89
+
90
+ RSpec/VerifiedDoubles:
91
+ Enabled: false
92
+
93
+ RSpec/SpecFilePathFormat:
94
+ Enabled: false
95
+
96
+ RSpec/DescribeClass:
97
+ Exclude:
98
+ - 'spec/e2e/**/*'
99
+
100
+ RSpec/BeforeAfterAll:
101
+ Exclude:
102
+ - 'spec/e2e/**/*'
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'pry'
9
+ gem 'pry-byebug'
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Josh Frye
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,366 @@
1
+ # Crucible
2
+
3
+ A Ruby MCP (Model Context Protocol) server for browser automation using [Ferrum](https://github.com/rubycdp/ferrum) and headless Chrome. Provides 29 tools that AI agents can use to control browsers, with built-in stealth mode to evade bot detection.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ git clone https://github.com/joshfng/crucible.git
9
+ cd crucible
10
+ bundle install
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Running the Server
16
+
17
+ ```bash
18
+ # Run with defaults (headless, 1280x720 viewport, 30s timeout)
19
+ ./exe/crucible
20
+
21
+ # Run with visible browser
22
+ ./exe/crucible --no-headless
23
+
24
+ # Full options
25
+ ./exe/crucible \
26
+ --no-headless \
27
+ --width 1920 \
28
+ --height 1080 \
29
+ --timeout 60 \
30
+ --chrome /usr/bin/chromium \
31
+ --error-level debug
32
+ ```
33
+
34
+ ### CLI Options
35
+
36
+ | Option | Description | Default |
37
+ | --------------------------- | ------------------------------------------- | ----------- |
38
+ | `-c, --config FILE` | Path to YAML configuration file | auto-detect |
39
+ | `--[no-]headless` | Run browser in headless mode | `true` |
40
+ | `-w, --width WIDTH` | Viewport width in pixels | `1280` |
41
+ | `-h, --height HEIGHT` | Viewport height in pixels | `720` |
42
+ | `--chrome PATH` | Path to Chrome/Chromium executable | auto-detect |
43
+ | `-t, --timeout SECONDS` | Default timeout in seconds | `30` |
44
+ | `--error-level LEVEL` | Logging level (debug/info/warn/error) | `warn` |
45
+ | `--screenshot-format FMT` | Default screenshot format (png/jpeg/base64) | `png` |
46
+ | `--content-format FMT` | Default content format (html/text) | `html` |
47
+ | `--[no-]stealth` | Enable/disable stealth mode | `true` |
48
+ | `--stealth-profile PROFILE` | Stealth profile (minimal/moderate/maximum) | `moderate` |
49
+ | `--stealth-locale LOCALE` | Browser locale for stealth mode | `en-US,en` |
50
+
51
+ ### Claude Code Integration
52
+
53
+ Add to your Claude Code MCP settings (`~/.claude/settings.json`):
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "crucible": {
59
+ "command": "ruby",
60
+ "args": [
61
+ "-I",
62
+ "/path/to/crucible/lib",
63
+ "/path/to/crucible/exe/crucible"
64
+ ]
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ## Tools
71
+
72
+ ### Navigation
73
+
74
+ | Tool | Description |
75
+ | ---------- | ----------------------------- |
76
+ | `navigate` | Navigate browser to a URL |
77
+ | `wait_for` | Wait for an element to appear |
78
+ | `back` | Navigate back in history |
79
+ | `forward` | Navigate forward in history |
80
+ | `refresh` | Refresh the current page |
81
+
82
+ ### Interaction
83
+
84
+ | Tool | Description |
85
+ | --------------- | ----------------------------------------------------- |
86
+ | `click` | Click an element (supports double-click, right-click) |
87
+ | `type` | Type text into an input (with optional clear/submit) |
88
+ | `fill_form` | Fill multiple form fields at once |
89
+ | `select_option` | Select option from dropdown |
90
+ | `scroll` | Scroll page or element into view |
91
+ | `hover` | Hover over an element |
92
+
93
+ ### Extraction
94
+
95
+ | Tool | Description |
96
+ | ------------- | -------------------------------------------------------------------------------- |
97
+ | `screenshot` | Take screenshot (viewport, full page, or element); save to file or return base64 |
98
+ | `get_content` | Get page content (HTML or text) |
99
+ | `pdf` | Generate PDF of the page; save to file or return base64 |
100
+ | `evaluate` | Execute JavaScript and return result |
101
+ | `get_url` | Get current page URL |
102
+ | `get_title` | Get current page title |
103
+
104
+ ### Cookies
105
+
106
+ | Tool | Description |
107
+ | --------------- | ---------------------------------- |
108
+ | `get_cookies` | Get all cookies or specific cookie |
109
+ | `set_cookies` | Set one or more cookies |
110
+ | `clear_cookies` | Clear all or specific cookies |
111
+
112
+ ### Sessions
113
+
114
+ | Tool | Description |
115
+ | --------------- | -------------------------------- |
116
+ | `list_sessions` | List all active browser sessions |
117
+ | `close_session` | Close a session or all sessions |
118
+
119
+ ### Downloads
120
+
121
+ | Tool | Description |
122
+ | ------------------- | ------------------------------------------------- |
123
+ | `set_download_path` | Set the directory for downloads |
124
+ | `wait_for_download` | Wait for a download to complete |
125
+ | `list_downloads` | List all tracked downloads |
126
+ | `clear_downloads` | Clear tracked downloads (optionally delete files) |
127
+
128
+ ### Stealth
129
+
130
+ | Tool | Description |
131
+ | --------------------- | ----------------------------------------------------- |
132
+ | `enable_stealth` | Enable stealth mode for a session |
133
+ | `disable_stealth` | Disable stealth mode for a session |
134
+ | `get_stealth_status` | Get stealth mode status for a session |
135
+ | `set_stealth_profile` | Change the stealth profile (minimal/moderate/maximum) |
136
+
137
+ ## Sessions
138
+
139
+ All tools accept an optional `session` parameter to manage multiple independent browser instances:
140
+
141
+ ```
142
+ # These run in separate browsers
143
+ navigate(session: "login-flow", url: "https://example.com/login")
144
+ navigate(session: "signup-flow", url: "https://example.com/signup")
145
+
146
+ # List active sessions
147
+ list_sessions()
148
+ # => { "count": 2, "sessions": ["login-flow", "signup-flow"] }
149
+
150
+ # Close a specific session
151
+ close_session(session: "login-flow")
152
+
153
+ # Close all sessions
154
+ close_session(all: true)
155
+ ```
156
+
157
+ Sessions are created automatically on first use and persist until explicitly closed.
158
+
159
+ ## Example Workflows
160
+
161
+ ### Basic Navigation
162
+
163
+ ```
164
+ navigate(url: "https://example.com")
165
+ wait_for(selector: ".content")
166
+ get_content(format: "text")
167
+ ```
168
+
169
+ ### Form Submission
170
+
171
+ ```
172
+ navigate(url: "https://example.com/login")
173
+ type(selector: "#email", text: "user@example.com")
174
+ type(selector: "#password", text: "secret123")
175
+ click(selector: "button[type=submit]")
176
+ wait_for(selector: ".dashboard")
177
+ ```
178
+
179
+ ### Screenshots & PDFs
180
+
181
+ ```
182
+ # Viewport screenshot (returns base64)
183
+ screenshot()
184
+
185
+ # Full page screenshot
186
+ screenshot(full_page: true)
187
+
188
+ # Element screenshot
189
+ screenshot(selector: ".hero-image")
190
+
191
+ # Save to file
192
+ screenshot(path: "/tmp/page.png")
193
+ screenshot(format: "jpeg", quality: 90, path: "/tmp/page.jpg")
194
+
195
+ # PDF generation
196
+ pdf() # Returns base64
197
+ pdf(path: "/tmp/page.pdf") # Save to file
198
+ pdf(format: "Letter", landscape: true) # Custom format
199
+ ```
200
+
201
+ ### JavaScript Execution
202
+
203
+ ```
204
+ # Get page dimensions
205
+ evaluate(expression: "[window.innerWidth, window.innerHeight]")
206
+
207
+ # Scroll to top
208
+ evaluate(expression: "window.scrollTo(0, 0)")
209
+
210
+ # Get element count
211
+ evaluate(expression: "document.querySelectorAll('a').length")
212
+ ```
213
+
214
+ ### File Downloads
215
+
216
+ ```
217
+ # Set download directory
218
+ set_download_path(path: "/tmp/downloads")
219
+
220
+ # Click download link and wait
221
+ click(selector: "a.download-btn")
222
+ wait_for_download(timeout: 30)
223
+
224
+ # List tracked downloads (persists across navigation)
225
+ list_downloads()
226
+
227
+ # Clear tracking and delete files
228
+ clear_downloads(delete_files: true)
229
+ ```
230
+
231
+ ## Stealth Mode
232
+
233
+ Stealth mode applies various evasion techniques to make headless Chrome appear as a regular browser to bot detection systems. It is enabled by default with the "moderate" profile.
234
+
235
+ ### Stealth Profiles
236
+
237
+ | Profile | Description |
238
+ | ---------- | ------------------------------------------------------- |
239
+ | `minimal` | Basic evasions (navigator.webdriver, window dimensions) |
240
+ | `moderate` | Common evasions for most use cases (default) |
241
+ | `maximum` | All evasions for strictest bot detection |
242
+
243
+ ### Evasions Applied
244
+
245
+ The stealth module includes evasions ported from [puppeteer-extra](https://github.com/berstend/puppeteer-extra):
246
+
247
+ - `navigator.webdriver` - Remove the webdriver flag
248
+ - `chrome.app` - Mock the chrome.app object
249
+ - `chrome.csi` - Mock the chrome.csi function
250
+ - `chrome.loadTimes` - Mock chrome.loadTimes
251
+ - `chrome.runtime` - Mock chrome.runtime for extensions
252
+ - `navigator.vendor` - Override navigator.vendor
253
+ - `navigator.languages` - Match Accept-Language header
254
+ - `navigator.plugins` - Mock plugins and mimeTypes
255
+ - `navigator.permissions` - Fix Notification.permission
256
+ - `navigator.hardwareConcurrency` - Set realistic core count
257
+ - `webgl.vendor` - Fix WebGL vendor/renderer
258
+ - `media.codecs` - Report support for proprietary codecs
259
+ - `iframe.contentWindow` - Fix iframe detection
260
+ - `window.outerdimensions` - Fix outerWidth/outerHeight
261
+ - User-Agent override - Strip "Headless" and fix platform
262
+
263
+ ### Runtime Control
264
+
265
+ ```
266
+ # Check stealth status
267
+ get_stealth_status(session: "default")
268
+
269
+ # Enable with maximum protection
270
+ enable_stealth(session: "default", profile: "maximum")
271
+
272
+ # Disable stealth (already-applied evasions remain)
273
+ disable_stealth(session: "default")
274
+
275
+ # Change profile
276
+ set_stealth_profile(session: "default", profile: "minimal")
277
+ ```
278
+
279
+ ### Configuration File
280
+
281
+ Create `~/.config/crucible/config.yml`:
282
+
283
+ ```yaml
284
+ browser:
285
+ headless: true
286
+ window_size: [1280, 720]
287
+
288
+ stealth:
289
+ enabled: true
290
+ profile: moderate
291
+ locale: "en-US,en"
292
+
293
+ server:
294
+ log_level: info
295
+ ```
296
+
297
+ ## Project Structure
298
+
299
+ ```
300
+ crucible/
301
+ ├── exe/crucible # CLI executable
302
+ ├── crucible.gemspec # Gem specification
303
+ ├── Gemfile # Dependencies
304
+ ├── Rakefile # Build tasks
305
+ ├── lib/
306
+ │ ├── crucible.rb # Main module
307
+ │ └── crucible/
308
+ │ ├── version.rb # Version constant
309
+ │ ├── configuration.rb # Config with YAML support
310
+ │ ├── session_manager.rb # Multi-session management
311
+ │ ├── server.rb # MCP server setup
312
+ │ ├── stealth.rb # Stealth mode module
313
+ │ ├── stealth/
314
+ │ │ ├── utils.js # Stealth utilities
315
+ │ │ └── evasions/ # Individual evasion scripts
316
+ │ └── tools/
317
+ │ ├── helpers.rb # Shared utilities
318
+ │ ├── navigation.rb # Navigation tools
319
+ │ ├── interaction.rb # Interaction tools
320
+ │ ├── extraction.rb # Extraction tools
321
+ │ ├── cookies.rb # Cookie tools
322
+ │ ├── sessions.rb # Session tools
323
+ │ ├── downloads.rb # Download tools
324
+ │ └── stealth.rb # Stealth control tools
325
+ └── spec/ # RSpec tests
326
+ ```
327
+
328
+ ## Development
329
+
330
+ ```bash
331
+ # Run tests
332
+ bundle exec rspec
333
+
334
+ # Run linter
335
+ bundle exec rubocop
336
+
337
+ # Interactive console
338
+ bundle exec rake console
339
+
340
+ # Run server in development
341
+ bundle exec rake server
342
+ ```
343
+
344
+ ## Releasing
345
+
346
+ ```bash
347
+ bin/release 0.2.0
348
+ git push origin main --tags
349
+ gh release create v0.2.0 --generate-notes
350
+ ```
351
+
352
+ The release workflow automatically publishes to RubyGems.
353
+
354
+ **Setup**: Add `RUBYGEMS_API_KEY` to repository secrets.
355
+
356
+ ## Requirements
357
+
358
+ - Ruby >= 3.2.0
359
+ - Chrome or Chromium browser
360
+ - Dependencies:
361
+ - `ferrum` ~> 0.15
362
+ - `mcp` ~> 0.4
363
+
364
+ ## License
365
+
366
+ MIT License. See [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: %i[spec rubocop]
11
+
12
+ desc 'Run the MCP server'
13
+ task :server do
14
+ exec 'ruby', '-I', 'lib', 'exe/crucible'
15
+ end
16
+
17
+ desc 'Open an interactive console'
18
+ task :console do
19
+ require 'irb'
20
+ require_relative 'lib/crucible'
21
+ ARGV.clear
22
+ IRB.start(__FILE__)
23
+ end