nuntius-rb 1.0.0.pre

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e94e856798181b8d99d873394ea2dacfcdfbe5ee04e10933dbf45145ee7f09b0
4
+ data.tar.gz: e742055df4793f45133dc3116f0d8fb6f31b417fa361db28605b58d137c90ba6
5
+ SHA512:
6
+ metadata.gz: f59b7bdcc9d294d42458499f701894c90d473a38bfd3866f54a883c670380263175951acf898464156b8e943ab083ea2160b5c600f27aefa89a11623e099cd72
7
+ data.tar.gz: cf853e0af5eeb9352b3b697083a279a1c627c958aba7b7f33ad326745c3252ac64b838e2b5685871fa55ad2279b7ac0d274c24401a93efe02aba672fd97cabc8
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vesper
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,315 @@
1
+ # Vesper
2
+ <img src="website/assets/readme-header.png" alt="Vesper" width="100%">
3
+
4
+ <br>
5
+
6
+ [![Gem](https://img.shields.io/gem/v/nuntius-rb?style=flat-square&label=gem)](https://rubygems.org/gems/nuntius-rb)
7
+ ![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.3-cc342d?style=flat-square)
8
+ [![License](https://img.shields.io/badge/license-MIT-2f4858?style=flat-square)](LICENSE)
9
+ ![Tests](https://img.shields.io/badge/tests-passing-2e7d32?style=flat-square)
10
+
11
+ <br>
12
+
13
+ Ruby client for Gemini `generateContent`. Includes a CLI and a PR review app.
14
+
15
+ <br>
16
+
17
+ # Installation
18
+
19
+ <br>
20
+
21
+ ```bash
22
+ gem install nuntius-rb
23
+ ```
24
+
25
+ <br>
26
+
27
+ With Bundler:
28
+
29
+ <br>
30
+
31
+ ```ruby
32
+ gem 'nuntius-rb', require: 'vesper'
33
+ ```
34
+
35
+ <br>
36
+
37
+ The package is published as `nuntius-rb`. The runtime entrypoint is `vesper`.
38
+
39
+ <br>
40
+
41
+ Set your API key in `.env`:
42
+
43
+ <br>
44
+
45
+ ```
46
+ GEMINI_API_KEY=your_api_key
47
+ ```
48
+
49
+ <br>
50
+
51
+ > [!NOTE]
52
+ > Ensure your API key is kept secure and not committed to version control.
53
+
54
+ <br>
55
+
56
+ # Usage
57
+
58
+ <br>
59
+
60
+ # Basic Setup
61
+
62
+ <br>
63
+
64
+ ```ruby
65
+ require 'vesper'
66
+ GeminiAI.load_env
67
+
68
+ client = GeminiAI::Client.new
69
+ puts client.generate_text('Write a haiku about Ruby')
70
+ ```
71
+
72
+ <br>
73
+
74
+ Use a different model when needed:
75
+
76
+ <br>
77
+
78
+ ```ruby
79
+ fast_client = GeminiAI::Client.new(model: :flash)
80
+ puts fast_client.generate_text('Explain Ruby in one sentence')
81
+ ```
82
+
83
+ <br>
84
+
85
+ # generateContent Text Models
86
+
87
+ <br>
88
+
89
+ | Key | ID |
90
+ | --- | --- |
91
+ | `:flash_latest` | `gemini-flash-latest` |
92
+ | `:pro_latest` | `gemini-pro-latest` |
93
+ | `:flash_3_5` | `gemini-3.5-flash` |
94
+ | `:pro_3_preview` | `gemini-3-pro-preview` |
95
+ | `:flash_3_preview` | `gemini-3-flash-preview` |
96
+ | `:pro_3_1_preview` | `gemini-3.1-pro-preview` |
97
+ | `:flash_3_1_lite` | `gemini-3.1-flash-lite` |
98
+ | `:pro_2_5` | `gemini-2.5-pro` |
99
+ | `:flash_2_5` | `gemini-2.5-flash` |
100
+ | `:flash_2_0` | `gemini-2.0-flash` |
101
+
102
+ <br>
103
+
104
+ Short aliases: `:pro` uses `gemini-pro-latest`, `:flash` uses `gemini-3.5-flash`, and `:flash_lite` uses `gemini-3.1-flash-lite`. Legacy `:pro_2_0` maps to `gemini-2.0-flash`.
105
+
106
+ <br>
107
+
108
+ The gem does not wrap embeddings, Imagen, or Veo APIs.
109
+
110
+ <br>
111
+
112
+ # Capabilities
113
+
114
+ <br>
115
+
116
+ Vesper supports text generation, chat, image input for `generateContent`, model aliases, safety settings, API key masking, retries, and a local CLI.
117
+
118
+ <br>
119
+
120
+ # Handling Errors
121
+
122
+ <br>
123
+
124
+ Client validation and API failures raise `GeminiAI::Error` with a readable message.
125
+ HTTP 429 responses are retried automatically up to three times with exponential backoff.
126
+
127
+ <br>
128
+
129
+ ```ruby
130
+ begin
131
+ response = client.generate_text('Hello')
132
+ puts response
133
+ rescue GeminiAI::Error => err
134
+ warn "Generation failed: #{err.message}"
135
+ end
136
+ ```
137
+
138
+ <br>
139
+
140
+ Common failures include:
141
+
142
+ <br>
143
+
144
+ - Missing or invalid `GEMINI_API_KEY`
145
+ - Empty prompts
146
+ - Prompts over the configured maximum length
147
+ - Gemini API errors returned by the service
148
+ - Network errors raised by HTTParty
149
+
150
+ <br>
151
+
152
+ # Retries
153
+
154
+ <br>
155
+
156
+ Rate-limit responses (`429`) are retried up to three times with waits of 5, 10, and 20 seconds.
157
+
158
+ <br>
159
+
160
+ # Timeouts
161
+
162
+ <br>
163
+
164
+ Requests use a 30 second HTTParty timeout.
165
+
166
+ <br>
167
+
168
+ # Logging
169
+
170
+ <br>
171
+
172
+ ```ruby
173
+ require 'vesper'
174
+
175
+ GeminiAI::Client.logger.level = Logger::INFO
176
+ client = GeminiAI::Client.new
177
+ ```
178
+
179
+ <br>
180
+
181
+ # Requirements
182
+
183
+ <br>
184
+
185
+ Ruby 3.3 or later. Linux and macOS are tested.
186
+
187
+ <br>
188
+
189
+ # Environment Variables
190
+
191
+ <br>
192
+
193
+ ```bash
194
+ GEMINI_API_KEY=your_api_key_here
195
+ ```
196
+
197
+ <br>
198
+
199
+ # Repo CLI
200
+
201
+ <br>
202
+
203
+ ```bash
204
+ ./bin/gemini test
205
+ ./bin/gemini generate "Your prompt"
206
+ ./bin/gemini chat
207
+ ```
208
+
209
+ <br>
210
+
211
+ # Local Development & Testing
212
+
213
+ <br>
214
+
215
+ ```bash
216
+ bundle exec rake test # Run tests
217
+ bundle exec rake docs # Build API docs
218
+ gem build nuntius-rb.gemspec
219
+ ```
220
+
221
+ <br>
222
+
223
+ # Review App
224
+
225
+ <br>
226
+
227
+ Vesper Review is the PR review app in this repo. It defaults to `gemini-3.5-flash`; retrieval context is off unless enabled in `vesper/config.yaml`.
228
+
229
+ <br>
230
+
231
+ For setup details, see [`vesper/Vesper.md`](vesper/Vesper.md).
232
+
233
+ <br>
234
+
235
+ # Examples
236
+
237
+ <br>
238
+
239
+ # Text Generation
240
+
241
+ <br>
242
+
243
+ ```ruby
244
+ client = GeminiAI::Client.new
245
+ puts client.generate_text('Write a haiku about Ruby')
246
+ ```
247
+
248
+ <br>
249
+
250
+ # Image Analysis
251
+
252
+ <br>
253
+
254
+ ```ruby
255
+ image_data = Base64.strict_encode64(File.binread('path/to/image.jpg'))
256
+ puts client.generate_image_text(image_data, 'Describe this image')
257
+ ```
258
+
259
+ <br>
260
+
261
+ # Chat
262
+
263
+ <br>
264
+
265
+ ```ruby
266
+ messages = [
267
+ { role: 'user', content: 'Hello!' },
268
+ { role: 'model', content: 'Hi there!' },
269
+ { role: 'user', content: 'Tell me about Ruby.' }
270
+ ]
271
+ puts client.chat(messages, system_instruction: 'Be concise.')
272
+ ```
273
+
274
+ <br>
275
+
276
+ # Documentation
277
+
278
+ <br>
279
+
280
+ | Need | Link |
281
+ | --- | --- |
282
+ | Start | [`Quickstart`](docs/start/quickstart.md) |
283
+ | API | [`Reference`](docs/reference/api.md) |
284
+ | Recipes | [`Cookbook`](docs/reference/cookbook.md) |
285
+ | Practice | [`Best practices`](docs/guides/practices.md) |
286
+ | Automation | [`Workflows`](docs/guides/workflows.md) |
287
+ | Project | [`Contributing`](docs/CONTRIBUTING.md) |
288
+
289
+ <br>
290
+
291
+ # Contributing
292
+
293
+ <br>
294
+
295
+ Fork the repo and open a pull request.
296
+
297
+ <br>
298
+
299
+ # License
300
+
301
+ <br>
302
+
303
+ MIT → see [`LICENSE`](LICENSE).
304
+
305
+ <br>
306
+
307
+ <p align="center">
308
+ <a href="https://github.com/apps/vesper-review">
309
+ <img src="website/favicon.svg" alt="Vesper Review app" width="96">
310
+ </a>
311
+
312
+ <br>
313
+
314
+ <a href="https://github.com/apps/vesper-review"><code>Vesper Review</code></a>
315
+ </p>
@@ -0,0 +1,288 @@
1
+ # frozen_string_literal: true
2
+ # SPDX-License-Identifier: MIT
3
+ # Copyright (c) 2026 vesper
4
+
5
+ require 'httparty'
6
+ require 'json'
7
+ require 'base64'
8
+ require 'logger'
9
+ require 'dotenv/load'
10
+ require_relative 'errors'
11
+ require_relative '../utils/moderation'
12
+
13
+ module GeminiAI
14
+ # Core client class for Gemini AI API communication
15
+ class Client
16
+ BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models'
17
+ # generateContent text model aliases.
18
+ MODELS = {
19
+ flash_latest: 'gemini-flash-latest',
20
+ pro_latest: 'gemini-pro-latest',
21
+ flash_3_5: 'gemini-3.5-flash',
22
+
23
+ pro_3_preview: 'gemini-3-pro-preview',
24
+ flash_3_preview: 'gemini-3-flash-preview',
25
+ pro_3_1_preview: 'gemini-3.1-pro-preview',
26
+ flash_3_1_lite: 'gemini-3.1-flash-lite',
27
+ pro_2_5: 'gemini-2.5-pro',
28
+ flash_2_5: 'gemini-2.5-flash',
29
+ flash_2_0: 'gemini-2.0-flash',
30
+
31
+ # Short aliases.
32
+ pro: 'gemini-pro-latest',
33
+ flash: 'gemini-3.5-flash',
34
+ flash_lite: 'gemini-3.1-flash-lite',
35
+ pro_2_0: 'gemini-2.0-flash'
36
+ }.freeze
37
+
38
+ # Deprecated models removed in this version (log warning and default to :pro)
39
+ DEPRECATED_MODELS = {
40
+ pro_1_5: 'gemini-1.5-pro',
41
+ flash_1_5: 'gemini-1.5-flash',
42
+ flash_8b: 'gemini-1.5-flash-8b'
43
+ }.freeze
44
+
45
+ # Configure logging
46
+ def self.logger
47
+ @logger ||= Logger.new($stdout).tap do |log|
48
+ log.level = Logger::DEBUG
49
+ log.formatter = proc do |severity, datetime, _progname, msg|
50
+ # Mask any potential API key in logs
51
+ masked_msg = msg.to_s.gsub(/AIza[a-zA-Z0-9_-]{35,}/, '[REDACTED]')
52
+ "#{datetime}: #{severity} -- #{masked_msg}\n"
53
+ end
54
+ end
55
+ end
56
+
57
+ def initialize(api_key = nil, model: :pro)
58
+ # Prioritize passed API key, then environment variable
59
+ @api_key = api_key || ENV.fetch('GEMINI_API_KEY', nil)
60
+
61
+ # Rate limiting - track last request time
62
+ @last_request_time = nil
63
+ # More conservative rate limiting in CI environments
64
+ @min_request_interval = ENV['CI'] == 'true' || ENV['GITHUB_ACTIONS'] == 'true' ? 3.0 : 1.0
65
+
66
+ # Extensive logging for debugging
67
+ self.class.logger.debug('Initializing Client')
68
+ self.class.logger.debug("API Key present: #{!@api_key.nil?}")
69
+ self.class.logger.debug("API Key length: #{@api_key&.length}")
70
+
71
+ # Validate API key before proceeding
72
+ validate_api_key!
73
+
74
+ @model = resolve_model(model)
75
+
76
+ self.class.logger.debug("Selected model: #{@model}")
77
+ end
78
+
79
+ def generate_text(prompt, options = {})
80
+ validate_prompt!(prompt)
81
+
82
+ request_body = {
83
+ contents: [{ parts: [{ text: prompt }] }],
84
+ generationConfig: build_generation_config(options)
85
+ }
86
+
87
+ # Add safety settings if provided
88
+ if options[:safety_settings]
89
+ request_body[:safetySettings] = options[:safety_settings].map do |setting|
90
+ {
91
+ category: setting[:category],
92
+ threshold: setting[:threshold]
93
+ }
94
+ end
95
+ end
96
+
97
+ apply_moderation(send_request(request_body), options)
98
+ end
99
+
100
+ def generate_image_text(image_base64, prompt, options = {})
101
+ raise Error, 'Image is required' if image_base64.nil? || image_base64.empty?
102
+
103
+ request_body = {
104
+ contents: [
105
+ { parts: [
106
+ { inline_data: { mime_type: 'image/jpeg', data: image_base64 } },
107
+ { text: prompt }
108
+ ] }
109
+ ],
110
+ generationConfig: build_generation_config(options)
111
+ }
112
+
113
+ # Use the pro model for image-to-text tasks
114
+ apply_moderation(send_request(request_body, model: :pro), options)
115
+ end
116
+
117
+ def chat(messages, options = {})
118
+ request_body = {
119
+ contents: messages.map { |msg| { role: msg[:role], parts: [{ text: msg[:content] }] } },
120
+ generationConfig: build_generation_config(options)
121
+ }
122
+
123
+ # Add system instruction if provided
124
+ if options[:system_instruction]
125
+ request_body[:systemInstruction] = {
126
+ parts: [
127
+ { text: options[:system_instruction] }
128
+ ]
129
+ }
130
+ end
131
+
132
+ apply_moderation(send_request(request_body), options)
133
+ end
134
+
135
+ private
136
+
137
+ def apply_moderation(response, options)
138
+ if options[:moderate]
139
+ moderated, warnings = Utils::Moderation.moderate_text(response)
140
+ warnings.each { |w| self.class.logger.warn(w) } unless warnings.empty?
141
+ moderated
142
+ else
143
+ response
144
+ end
145
+ end
146
+
147
+ def resolve_model(model)
148
+ if DEPRECATED_MODELS.key?(model)
149
+ self.class.logger.warn("Model #{model} (#{DEPRECATED_MODELS[model]}) is deprecated and has been removed. " \
150
+ 'Defaulting to :pro (gemini-pro-latest). Please update your code to use supported models.')
151
+ MODELS[:pro]
152
+ else
153
+ MODELS.fetch(model) do
154
+ self.class.logger.warn("Invalid model: #{model}, defaulting to pro")
155
+ MODELS[:pro]
156
+ end
157
+ end
158
+ end
159
+
160
+ def validate_api_key!
161
+ if @api_key.nil? || @api_key.to_s.strip.empty?
162
+ self.class.logger.error('API key is missing')
163
+ raise Error, 'API key is required. Set GEMINI_API_KEY environment variable or pass key directly.'
164
+ end
165
+
166
+ # Optional: Add basic API key format validation
167
+ unless valid_api_key_format?(@api_key)
168
+ self.class.logger.error('Invalid API key format')
169
+ raise Error, 'Invalid API key format. Please check your key.'
170
+ end
171
+
172
+ # Optional: Check key length and complexity
173
+ return unless @api_key.length < 40
174
+
175
+ self.class.logger.warn('Potentially weak API key detected')
176
+ end
177
+
178
+ def valid_api_key_format?(key)
179
+ # Strict format check: starts with 'AIza', reasonable length
180
+ key =~ /^AIza[a-zA-Z0-9_-]{35,}$/
181
+ end
182
+
183
+ def validate_prompt!(prompt)
184
+ if prompt.nil? || prompt.strip.empty?
185
+ self.class.logger.error('Empty prompt provided')
186
+ raise Error, 'Prompt cannot be empty'
187
+ end
188
+
189
+ return unless prompt.length > 8192
190
+
191
+ self.class.logger.error('Prompt exceeds maximum length')
192
+ raise Error, 'Prompt too long (max 8192 tokens)'
193
+ end
194
+
195
+ def build_generation_config(options)
196
+ {
197
+ temperature: options[:temperature] || 0.7,
198
+ maxOutputTokens: options[:max_tokens] || 1024,
199
+ topP: options[:top_p] || 0.9,
200
+ topK: options[:top_k] || 40
201
+ }
202
+ end
203
+
204
+ def send_request(body, model: nil, retry_count: 0)
205
+ # Rate limiting - ensure minimum interval between requests
206
+ rate_limit_delay
207
+
208
+ current_model = model ? MODELS.fetch(model) { MODELS[:pro] } : @model
209
+ url = "#{BASE_URL}/#{current_model}:generateContent?key=#{@api_key}"
210
+
211
+ # Log URL with masked API key for security
212
+ masked_url = "#{BASE_URL}/#{current_model}:generateContent?key=#{mask_api_key(@api_key)}"
213
+ self.class.logger.debug("Request URL: #{masked_url}")
214
+ self.class.logger.debug("Request Body: #{body.to_json}")
215
+
216
+ begin
217
+ response = HTTParty.post(
218
+ url,
219
+ body: body.to_json,
220
+ headers: {
221
+ 'Content-Type' => 'application/json',
222
+ 'x-goog-api-client' => 'vesper_ruby_gem/0.1.0'
223
+ },
224
+ timeout: 30
225
+ )
226
+
227
+ self.class.logger.debug("Response Code: #{response.code}")
228
+ self.class.logger.debug("Response Body: #{response.body}")
229
+
230
+ parse_response(response, retry_count, body, model)
231
+ rescue HTTParty::Error, Net::OpenTimeout => e
232
+ self.class.logger.error("API request failed: #{e.message}")
233
+ raise Error, "API request failed: #{e.message}"
234
+ end
235
+ end
236
+
237
+ def parse_response(response, retry_count, body, model)
238
+ case response.code
239
+ when 200
240
+ text = response.parsed_response
241
+ .dig('candidates', 0, 'content', 'parts', 0, 'text')
242
+ text || 'No response generated'
243
+ when 429
244
+ # Rate limit exceeded - implement exponential backoff
245
+ max_retries = 3
246
+ if retry_count < max_retries
247
+ wait_time = (2**retry_count) * 5 # 5, 10, 20 seconds
248
+ self.class.logger.warn("Rate limit hit (429). Retrying in #{wait_time}s (attempt #{retry_count + 1}/#{max_retries})")
249
+ sleep(wait_time)
250
+ send_request(body, model: model, retry_count: retry_count + 1)
251
+ else
252
+ self.class.logger.error("Rate limit exceeded after #{max_retries} retries")
253
+ raise Error, 'Rate limit exceeded. Please check your quota and billing details.'
254
+ end
255
+ else
256
+ error_message = response.parsed_response['error']&.dig('message') || response.body
257
+ self.class.logger.error("API Error: #{error_message}")
258
+ raise Error, "API Error: #{error_message}"
259
+ end
260
+ end
261
+
262
+ # Rate limiting to prevent hitting API limits
263
+ def rate_limit_delay
264
+ current_time = Time.now
265
+
266
+ if @last_request_time
267
+ time_since_last = current_time - @last_request_time
268
+ if time_since_last < @min_request_interval
269
+ sleep_time = @min_request_interval - time_since_last
270
+ self.class.logger.debug("Rate limiting: sleeping #{sleep_time.round(2)}s")
271
+ sleep(sleep_time)
272
+ end
273
+ end
274
+
275
+ @last_request_time = Time.now
276
+ end
277
+
278
+ # Mask API key for logging and error reporting
279
+ def mask_api_key(key)
280
+ return '[REDACTED]' if key.nil?
281
+
282
+ # Keep first 4 and last 4 characters, replace middle with asterisks
283
+ return key if key.length <= 8
284
+
285
+ "#{key[0, 4]}#{'*' * (key.length - 8)}#{key[-4, 4]}"
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,24 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module GeminiAI
7
+ # Base error class for all GeminiAI related errors
8
+ class Error < StandardError; end
9
+
10
+ # API related errors
11
+ class APIError < Error; end
12
+
13
+ # Authentication errors
14
+ class AuthenticationError < Error; end
15
+
16
+ # Rate limit errors
17
+ class RateLimitError < Error; end
18
+
19
+ # Invalid request errors
20
+ class InvalidRequestError < Error; end
21
+
22
+ # Network/connection errors
23
+ class NetworkError < Error; end
24
+ end
@@ -0,0 +1,8 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module GeminiAI
7
+ VERSION = '1.0.0.pre'
8
+ end
data/lib/gemini.rb ADDED
@@ -0,0 +1,23 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ # frozen_string_literal: true
5
+
6
+ # Main entry point for the GeminiAI gem
7
+ require_relative 'core/version'
8
+ require_relative 'core/errors'
9
+ require_relative 'core/client'
10
+ require_relative 'utils/loader'
11
+ require_relative 'utils/logger'
12
+
13
+ module GeminiAI
14
+ # Convenience method to create a new client
15
+ def self.new(api_key = nil, model: :pro)
16
+ Client.new(api_key, model: model)
17
+ end
18
+
19
+ # Load environment variables
20
+ def self.load_env(file_path = '.env')
21
+ Utils::Loader.load(file_path)
22
+ end
23
+ end
data/lib/mac/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Mac Utils
2
+
3
+ <br>
4
+
5
+ This module provides macOS-specific utilities for the vesper gem.
6
+
7
+ <br>
8
+
9
+ # Methods
10
+
11
+ <br>
12
+
13
+ # `GeminiAI::MacUtils.mac?`
14
+
15
+ <br>
16
+
17
+ Returns `true` if the current platform is macOS (Darwin), `false` otherwise.
18
+
19
+ <br>
20
+
21
+ # `GeminiAI::MacUtils.version`
22
+
23
+ <br>
24
+
25
+ Returns the macOS version string (e.g., "14.1") if running on macOS, `nil` otherwise.
26
+
27
+ <br>
28
+
29
+ # Usage
30
+
31
+ <br>
32
+
33
+ ```ruby
34
+ require 'mac/mac_utils'
35
+
36
+ if GeminiAI::MacUtils.mac?
37
+ puts "Running on macOS version #{GeminiAI::MacUtils.version}"
38
+ end
39
+ ```
@@ -0,0 +1,15 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ module GeminiAI
5
+ module MacUtils
6
+ def self.mac?
7
+ require 'rbconfig'
8
+ RbConfig::CONFIG['host_os'] =~ /darwin/
9
+ end
10
+
11
+ def self.version
12
+ `/usr/bin/sw_vers -productVersion`.strip if mac?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module GeminiAI
7
+ module Utils
8
+ # Utility class for loading environment variables from .env files
9
+ class Loader
10
+ def self.load(file_path = '.env')
11
+ return unless File.exist?(file_path)
12
+
13
+ File.readlines(file_path).each do |line|
14
+ line = line.strip
15
+ next if line.empty? || line.start_with?('#')
16
+
17
+ key, value = line.split('=', 2)
18
+ ENV[key] = value if key && value
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ # frozen_string_literal: true
5
+
6
+ require 'logger'
7
+
8
+ module GeminiAI
9
+ module Utils
10
+ # Centralized logging utility
11
+ class Logger
12
+ def self.instance
13
+ @instance ||= ::Logger.new($stdout).tap do |log|
14
+ log.level = ::Logger::INFO
15
+ log.formatter = proc do |severity, datetime, _progname, msg|
16
+ # Mask any potential API key in logs
17
+ masked_msg = msg.to_s.gsub(/AIza[a-zA-Z0-9_-]{35,}/, '[REDACTED]')
18
+ "#{datetime}: #{severity} -- #{masked_msg}\n"
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.debug(message)
24
+ instance.debug(message)
25
+ end
26
+
27
+ def self.info(message)
28
+ instance.info(message)
29
+ end
30
+
31
+ def self.warn(message)
32
+ instance.warn(message)
33
+ end
34
+
35
+ def self.error(message)
36
+ instance.error(message)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeminiAI
4
+ module Utils
5
+ # Content moderation utility for filtering potentially harmful or inappropriate content
6
+ class Moderation
7
+ # Patterns for detecting potentially harmful content
8
+ HARMFUL_PATTERNS = [
9
+ /\b(hack|exploit|malware|virus|trojan|ransomware)/i,
10
+ /\b(illegal|unlawful|criminal)/i,
11
+ /\b(violence|kill|harm|attack)/i,
12
+ /\b(drug|weapon|nuclear)/i
13
+ ].freeze
14
+
15
+ # Moderate text by checking for harmful patterns and redacting them
16
+ # Returns [moderated_text, warnings_array]
17
+ def self.moderate_text(text)
18
+ return [text, []] unless text.is_a?(String)
19
+
20
+ moderated = text.dup
21
+ warnings = []
22
+
23
+ HARMFUL_PATTERNS.each do |pattern|
24
+ if moderated.gsub!(pattern, '[REDACTED]')
25
+ warnings << "Detected potentially harmful pattern: #{pattern.inspect}"
26
+ end
27
+ end
28
+
29
+ [moderated, warnings]
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/vesper.rb ADDED
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 vesper
3
+
4
+ # frozen_string_literal: true
5
+
6
+ # Main entry point for the vesper gem
7
+ require_relative 'gemini'
metadata ADDED
@@ -0,0 +1,216 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nuntius-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre
5
+ platform: ruby
6
+ authors:
7
+ - Niladri Das
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.21'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '0.25'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.21'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.25'
33
+ - !ruby/object:Gem::Dependency
34
+ name: logger
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: dotenv
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.2'
61
+ - !ruby/object:Gem::Dependency
62
+ name: minitest
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '5.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rake
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 13.4.2
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 13.4.2
89
+ - !ruby/object:Gem::Dependency
90
+ name: github-markup
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '6.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '6.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: redcarpet
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.6'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.6'
117
+ - !ruby/object:Gem::Dependency
118
+ name: yard
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 0.9.34
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.9.34
131
+ - !ruby/object:Gem::Dependency
132
+ name: rubocop
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: 1.87.0
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: 1.87.0
145
+ - !ruby/object:Gem::Dependency
146
+ name: rubocop-minitest
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: 0.39.1
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: 0.39.1
159
+ - !ruby/object:Gem::Dependency
160
+ name: rubocop-rake
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: 0.7.1
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: 0.7.1
173
+ description: Provides text generation with Google's Gemini AI models
174
+ email:
175
+ - bniladridas@gmail.com
176
+ executables: []
177
+ extensions: []
178
+ extra_rdoc_files: []
179
+ files:
180
+ - LICENSE
181
+ - README.md
182
+ - lib/core/client.rb
183
+ - lib/core/errors.rb
184
+ - lib/core/version.rb
185
+ - lib/gemini.rb
186
+ - lib/mac/README.md
187
+ - lib/mac/mac_utils.rb
188
+ - lib/utils/loader.rb
189
+ - lib/utils/logger.rb
190
+ - lib/utils/moderation.rb
191
+ - lib/vesper.rb
192
+ homepage: https://github.com/bniladridas/vesper
193
+ licenses:
194
+ - MIT
195
+ metadata:
196
+ rubygems_mfa_required: 'true'
197
+ post_install_message:
198
+ rdoc_options: []
199
+ require_paths:
200
+ - lib
201
+ required_ruby_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: 3.3.0
206
+ required_rubygems_version: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">"
209
+ - !ruby/object:Gem::Version
210
+ version: 1.3.1
211
+ requirements: []
212
+ rubygems_version: 3.0.3.1
213
+ signing_key:
214
+ specification_version: 4
215
+ summary: A Ruby gem for interacting with Google's Gemini AI models
216
+ test_files: []