spyglasses 1.0.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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rspec_status +54 -0
- data/CHANGELOG.md +41 -0
- data/DEVELOPMENT.md +215 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +143 -0
- data/LICENSE +21 -0
- data/README.md +335 -0
- data/Rakefile +37 -0
- data/lib/spyglasses/client.rb +491 -0
- data/lib/spyglasses/configuration.rb +79 -0
- data/lib/spyglasses/middleware.rb +197 -0
- data/lib/spyglasses/types.rb +210 -0
- data/lib/spyglasses/version.rb +5 -0
- data/lib/spyglasses.rb +28 -0
- metadata +203 -0
data/README.md
ADDED
@@ -0,0 +1,335 @@
|
|
1
|
+
# Spyglasses Ruby Gem
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/spyglasses)
|
4
|
+
[](https://github.com/spyglasses/spyglasses-ruby/actions/workflows/ruby.yml)
|
5
|
+
|
6
|
+
AI Agent Detection and Management for Ruby web applications. Spyglasses provides comprehensive AI agent detection and management capabilities for Ruby web applications, including Rails, Sinatra, and other Rack-based frameworks.
|
7
|
+
|
8
|
+
## Features
|
9
|
+
|
10
|
+
- 🤖 **AI Bot Detection**: Automatically detect AI agents like GPTBot, ClaudeBot, ChatGPT-User, and more
|
11
|
+
- 🧠 **AI Referrer Detection**: Track traffic from AI platforms like ChatGPT, Claude, Perplexity
|
12
|
+
- 🚫 **Flexible Blocking**: Configure blocking rules via the Spyglasses platform
|
13
|
+
- 📊 **Request Logging**: Non-blocking request logging to Spyglasses collector
|
14
|
+
- ⚡ **High Performance**: Minimal overhead with pattern caching and background processing
|
15
|
+
- 🔧 **Easy Integration**: Drop-in Rack middleware for any Ruby web framework
|
16
|
+
- 🛡️ **Thread Safe**: Built for concurrent Ruby applications
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'spyglasses'
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
$ bundle install
|
30
|
+
```
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
```bash
|
35
|
+
$ gem install spyglasses
|
36
|
+
```
|
37
|
+
|
38
|
+
## Quick Start
|
39
|
+
|
40
|
+
### 1. Get Your API Key
|
41
|
+
|
42
|
+
Sign up at [spyglasses.io](https://spyglasses.io) to get your API key.
|
43
|
+
|
44
|
+
### 2. Set Environment Variable
|
45
|
+
|
46
|
+
```bash
|
47
|
+
export SPYGLASSES_API_KEY=your_api_key_here
|
48
|
+
```
|
49
|
+
|
50
|
+
### 3. Add Middleware
|
51
|
+
|
52
|
+
#### Rails
|
53
|
+
|
54
|
+
Add to your `config/application.rb`:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# config/application.rb
|
58
|
+
class Application < Rails::Application
|
59
|
+
config.middleware.use Spyglasses::Middleware
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
Or with options:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
# config/application.rb
|
67
|
+
class Application < Rails::Application
|
68
|
+
config.middleware.use Spyglasses::Middleware, {
|
69
|
+
api_key: ENV['SPYGLASSES_API_KEY'],
|
70
|
+
debug: Rails.env.development?,
|
71
|
+
exclude_paths: ['/admin', '/internal']
|
72
|
+
}
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
#### Sinatra
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
require 'sinatra'
|
80
|
+
require 'spyglasses'
|
81
|
+
|
82
|
+
use Spyglasses::Middleware, api_key: ENV['SPYGLASSES_API_KEY']
|
83
|
+
|
84
|
+
get '/' do
|
85
|
+
'Hello World!'
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
#### Rack Application
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# config.ru
|
93
|
+
require 'spyglasses'
|
94
|
+
|
95
|
+
use Spyglasses::Middleware, api_key: ENV['SPYGLASSES_API_KEY']
|
96
|
+
|
97
|
+
app = lambda do |env|
|
98
|
+
[200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
|
99
|
+
end
|
100
|
+
|
101
|
+
run app
|
102
|
+
```
|
103
|
+
|
104
|
+
## Configuration
|
105
|
+
|
106
|
+
### Environment Variables
|
107
|
+
|
108
|
+
| Variable | Description | Default |
|
109
|
+
|----------|-------------|---------|
|
110
|
+
| `SPYGLASSES_API_KEY` | Your Spyglasses API key | Required |
|
111
|
+
| `SPYGLASSES_DEBUG` | Enable debug logging | `false` |
|
112
|
+
| `SPYGLASSES_AUTO_SYNC` | Auto-sync patterns on startup | `true` |
|
113
|
+
| `SPYGLASSES_CACHE_TTL` | Pattern cache TTL in seconds | `86400` (24 hours) |
|
114
|
+
| `SPYGLASSES_COLLECT_ENDPOINT` | Custom collector endpoint | `https://www.spyglasses.io/api/collect` |
|
115
|
+
| `SPYGLASSES_PATTERNS_ENDPOINT` | Custom patterns endpoint | `https://www.spyglasses.io/api/patterns` |
|
116
|
+
|
117
|
+
### Middleware Options
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
use Spyglasses::Middleware, {
|
121
|
+
api_key: 'your-api-key', # API key (overrides env var)
|
122
|
+
debug: true, # Enable debug logging
|
123
|
+
auto_sync: true, # Auto-sync patterns
|
124
|
+
platform_type: 'rails', # Platform identifier
|
125
|
+
exclude_paths: [ # Paths to exclude from monitoring
|
126
|
+
'/admin',
|
127
|
+
'/api/internal',
|
128
|
+
/\.json$/ # Regex patterns supported
|
129
|
+
],
|
130
|
+
collect_endpoint: 'https://...', # Custom collector endpoint
|
131
|
+
patterns_endpoint: 'https://...' # Custom patterns endpoint
|
132
|
+
}
|
133
|
+
```
|
134
|
+
|
135
|
+
### Global Configuration
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# config/initializers/spyglasses.rb (Rails)
|
139
|
+
Spyglasses.configure do |config|
|
140
|
+
config.api_key = ENV['SPYGLASSES_API_KEY']
|
141
|
+
config.debug = Rails.env.development?
|
142
|
+
config.platform_type = 'rails'
|
143
|
+
config.exclude_paths = ['/admin', '/internal']
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
## Usage Examples
|
148
|
+
|
149
|
+
### Manual Detection
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
require 'spyglasses'
|
153
|
+
|
154
|
+
# Configure the client
|
155
|
+
client = Spyglasses::Client.new(
|
156
|
+
Spyglasses::Configuration.new.tap do |config|
|
157
|
+
config.api_key = 'your-api-key'
|
158
|
+
end
|
159
|
+
)
|
160
|
+
|
161
|
+
# Detect bots
|
162
|
+
result = client.detect_bot('GPTBot/1.0')
|
163
|
+
puts "Bot detected: #{result.is_bot}" # => true
|
164
|
+
puts "Should block: #{result.should_block}" # => depends on your settings
|
165
|
+
|
166
|
+
# Detect AI referrers
|
167
|
+
result = client.detect_ai_referrer('https://chat.openai.com/')
|
168
|
+
puts "AI referrer: #{result.source_type}" # => 'ai_referrer'
|
169
|
+
|
170
|
+
# Combined detection
|
171
|
+
result = client.detect('Mozilla/5.0', 'https://chat.openai.com/')
|
172
|
+
puts "Source type: #{result.source_type}" # => 'ai_referrer'
|
173
|
+
```
|
174
|
+
|
175
|
+
### Custom Rack Middleware
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
class MyCustomMiddleware
|
179
|
+
def initialize(app)
|
180
|
+
@app = app
|
181
|
+
@spyglasses = Spyglasses::Client.new
|
182
|
+
end
|
183
|
+
|
184
|
+
def call(env)
|
185
|
+
request = Rack::Request.new(env)
|
186
|
+
|
187
|
+
# Detect using Spyglasses
|
188
|
+
result = @spyglasses.detect(request.user_agent, request.referrer)
|
189
|
+
|
190
|
+
if result.is_bot && result.should_block
|
191
|
+
return [403, {}, ['Forbidden']]
|
192
|
+
end
|
193
|
+
|
194
|
+
# Log the request if something was detected
|
195
|
+
if result.source_type != 'none'
|
196
|
+
@spyglasses.log_request(result, {
|
197
|
+
url: request.url,
|
198
|
+
user_agent: request.user_agent,
|
199
|
+
# ... other request info
|
200
|
+
})
|
201
|
+
end
|
202
|
+
|
203
|
+
@app.call(env)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
## Blocking Configuration
|
209
|
+
|
210
|
+
Blocking rules are configured through the [Spyglasses platform](https://spyglasses.io) dashboard, not in code:
|
211
|
+
|
212
|
+
1. **Global AI Model Trainer Blocking**: Block all AI training bots (GPTBot, ClaudeBot, etc.)
|
213
|
+
2. **Custom Block Rules**: Block specific categories, subcategories, or individual patterns
|
214
|
+
3. **Custom Allow Rules**: Create exceptions for specific bots
|
215
|
+
|
216
|
+
The middleware automatically loads and applies these settings.
|
217
|
+
|
218
|
+
## Default Patterns
|
219
|
+
|
220
|
+
The gem includes default patterns for common AI agents:
|
221
|
+
|
222
|
+
### AI Assistants (Usually Allowed)
|
223
|
+
- ChatGPT-User/* - OpenAI ChatGPT user requests
|
224
|
+
- Claude-User/* - Anthropic Claude user requests
|
225
|
+
- Perplexity-User/* - Perplexity AI user requests
|
226
|
+
- Gemini-User/* - Google Gemini user requests
|
227
|
+
|
228
|
+
### AI Model Training Crawlers (Can be Blocked)
|
229
|
+
- GPTBot/* - OpenAI training crawler
|
230
|
+
- ClaudeBot/* - Anthropic training crawler
|
231
|
+
- CCBot/* - Common Crawl bot
|
232
|
+
- meta-externalagent/* - Meta training crawler
|
233
|
+
- Applebot-Extended/* - Apple training crawler
|
234
|
+
|
235
|
+
### AI Referrers
|
236
|
+
- chat.openai.com, chatgpt.com - ChatGPT
|
237
|
+
- claude.ai - Claude
|
238
|
+
- perplexity.ai - Perplexity
|
239
|
+
- gemini.google.com - Gemini
|
240
|
+
- copilot.microsoft.com - Microsoft Copilot
|
241
|
+
|
242
|
+
## Framework Integration
|
243
|
+
|
244
|
+
### Rails Integration
|
245
|
+
|
246
|
+
For Rails applications, you can also create an initializer:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
# config/initializers/spyglasses.rb
|
250
|
+
Spyglasses.configure do |config|
|
251
|
+
config.api_key = Rails.application.credentials.spyglasses_api_key
|
252
|
+
config.debug = Rails.env.development?
|
253
|
+
config.platform_type = 'rails'
|
254
|
+
config.exclude_paths = [
|
255
|
+
'/rails/active_storage',
|
256
|
+
'/admin',
|
257
|
+
/^\/api\/internal/
|
258
|
+
]
|
259
|
+
end
|
260
|
+
|
261
|
+
# Add middleware
|
262
|
+
Rails.application.config.middleware.use Spyglasses::Middleware
|
263
|
+
```
|
264
|
+
|
265
|
+
### Sinatra Integration
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
# app.rb
|
269
|
+
require 'sinatra'
|
270
|
+
require 'spyglasses'
|
271
|
+
|
272
|
+
configure do
|
273
|
+
Spyglasses.configure do |config|
|
274
|
+
config.api_key = ENV['SPYGLASSES_API_KEY']
|
275
|
+
config.platform_type = 'sinatra'
|
276
|
+
end
|
277
|
+
|
278
|
+
use Spyglasses::Middleware
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
## Testing
|
283
|
+
|
284
|
+
Add to your test helper:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
# spec/spec_helper.rb or test/test_helper.rb
|
288
|
+
require 'spyglasses'
|
289
|
+
|
290
|
+
# Disable API calls in tests
|
291
|
+
Spyglasses.configure do |config|
|
292
|
+
config.api_key = nil
|
293
|
+
config.auto_sync = false
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
Run the test suite:
|
298
|
+
|
299
|
+
```bash
|
300
|
+
$ bundle exec rspec
|
301
|
+
```
|
302
|
+
|
303
|
+
## Development
|
304
|
+
|
305
|
+
After checking out the repo, run:
|
306
|
+
|
307
|
+
```bash
|
308
|
+
$ bin/setup # Install dependencies
|
309
|
+
$ rake spec # Run tests
|
310
|
+
$ rake console # Interactive console
|
311
|
+
$ rake check # Run all checks (tests + linting)
|
312
|
+
```
|
313
|
+
|
314
|
+
## Performance
|
315
|
+
|
316
|
+
The Spyglasses middleware is designed for high-performance applications:
|
317
|
+
|
318
|
+
- **Pattern Caching**: Regex patterns are compiled and cached
|
319
|
+
- **Background Logging**: API calls are made in background threads
|
320
|
+
- **Minimal Overhead**: Typical overhead is <1ms per request
|
321
|
+
- **Smart Exclusions**: Static assets and health checks are automatically excluded
|
322
|
+
|
323
|
+
## Contributing
|
324
|
+
|
325
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/spyglasses/spyglasses-ruby.
|
326
|
+
|
327
|
+
## License
|
328
|
+
|
329
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
330
|
+
|
331
|
+
## Support
|
332
|
+
|
333
|
+
- 📧 Email: support@spyglasses.io
|
334
|
+
- 📖 Documentation: https://docs.spyglasses.io
|
335
|
+
- 🐛 Issues: https://github.com/spyglasses/spyglasses-ruby/issues
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
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
|
+
desc 'Run tests with coverage'
|
11
|
+
task :coverage do
|
12
|
+
ENV['COVERAGE'] = 'true'
|
13
|
+
Rake::Task[:spec].invoke
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Run all checks (tests, rubocop, etc.)'
|
17
|
+
task :check do
|
18
|
+
Rake::Task[:spec].invoke
|
19
|
+
Rake::Task[:rubocop].invoke
|
20
|
+
end
|
21
|
+
|
22
|
+
task default: :check
|
23
|
+
|
24
|
+
desc 'Build and install gem locally'
|
25
|
+
task :install_local do
|
26
|
+
Rake::Task[:build].invoke
|
27
|
+
gemfile = Dir.glob('pkg/*.gem').last
|
28
|
+
system "gem install #{gemfile}"
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'Run console with gem loaded'
|
32
|
+
task :console do
|
33
|
+
require 'bundler/setup'
|
34
|
+
require 'spyglasses'
|
35
|
+
require 'pry'
|
36
|
+
Pry.start
|
37
|
+
end
|