semantic_pen 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1b2846b64e0b8d626bf54c2da6590fd0aa0d18151be75064e6965d5b16184937
4
+ data.tar.gz: 88366157f967caa712db22d45cab9c6096484b01f4837be3a28e1fec6ebb130e
5
+ SHA512:
6
+ metadata.gz: cbcd7a114c6b0a453e6dbe3c13e0bb4f503fb2c547245844108b95591e6dc4a25e61d1f45ca641951358cef2a293a64a18c53ff4a5dbe525934fdcdfdc5a4b58
7
+ data.tar.gz: db7bd1a601fda864616da2f1ea51a057e01909d934f4084c3a8f8790b419b6ff82b5270e5f773656ff18e2c5e0d144141cc3332dc4b2643414c5a908e9fc6f54
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 SemanticPen Team
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,319 @@
1
+ # SemanticPen Ruby SDK
2
+
3
+ Official Ruby SDK for the SemanticPen API - AI-powered content generation made simple.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'semantic_pen'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ gem install semantic_pen
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```ruby
28
+ require 'semantic_pen'
29
+
30
+ # Initialize the client
31
+ client = SemanticPen::Client.new(api_key: 'your-api-key')
32
+
33
+ # Generate an article
34
+ response = client.generate_article('AI in healthcare', project_name: 'Medical Blog')
35
+
36
+ if response.success? && response.has_article_ids?
37
+ article_id = response.first_article_id
38
+
39
+ # Check article status
40
+ article = client.get_article(article_id)
41
+ puts "Status: #{article.status} (#{article.progress}% complete)"
42
+ end
43
+ ```
44
+
45
+ ## Configuration
46
+
47
+ ### Basic Configuration
48
+
49
+ ```ruby
50
+ client = SemanticPen::Client.new(api_key: 'your-api-key')
51
+ ```
52
+
53
+ ### Custom Configuration
54
+
55
+ ```ruby
56
+ client = SemanticPen::Client.new(
57
+ api_key: 'your-api-key',
58
+ base_url: 'https://api.semanticpen.com',
59
+ timeout: 120 # 2 minutes
60
+ )
61
+ ```
62
+
63
+ ### Global Configuration
64
+
65
+ ```ruby
66
+ SemanticPen.configure do |config|
67
+ config.api_key = 'your-api-key'
68
+ config.base_url = 'https://www.semanticpen.com'
69
+ config.timeout = 60
70
+ end
71
+ ```
72
+
73
+ ### Environment Variables
74
+
75
+ You can also configure the SDK using environment variables:
76
+
77
+ ```bash
78
+ export SEMANTIC_PEN_API_KEY=your-api-key
79
+ export SEMANTIC_PEN_BASE_URL=https://www.semanticpen.com
80
+ ```
81
+
82
+ ```ruby
83
+ client = SemanticPen::Client.new(
84
+ api_key: ENV['SEMANTIC_PEN_API_KEY'],
85
+ base_url: ENV['SEMANTIC_PEN_BASE_URL']
86
+ )
87
+ ```
88
+
89
+ ## API Methods
90
+
91
+ ### Generate Article
92
+
93
+ Generate a new article with AI-powered content:
94
+
95
+ ```ruby
96
+ response = client.generate_article('target keyword', project_name: 'My Project')
97
+ ```
98
+
99
+ **Parameters:**
100
+ - `target_keyword` (required): The main keyword for the article
101
+ - `project_name` (optional): Project name for organization
102
+
103
+ **Returns:** `SemanticPen::Models::GenerateArticleResponse`
104
+
105
+ ### Get Article Status
106
+
107
+ Retrieve the current status and content of an article:
108
+
109
+ ```ruby
110
+ article = client.get_article('article-id')
111
+ ```
112
+
113
+ **Parameters:**
114
+ - `article_id` (required): The ID of the article to retrieve
115
+
116
+ **Returns:** `SemanticPen::Models::Article`
117
+
118
+ ## Models
119
+
120
+ ### Article
121
+
122
+ ```ruby
123
+ article = client.get_article('article-id')
124
+
125
+ # Properties
126
+ article.id # Article ID
127
+ article.title # Article title
128
+ article.content # Article content
129
+ article.status # Current status
130
+ article.progress # Progress percentage (0-100)
131
+ article.target_keyword # Target keyword
132
+ article.project_name # Project name
133
+ article.created_at # Creation timestamp
134
+ article.updated_at # Last update timestamp
135
+
136
+ # Status methods
137
+ article.completed? # true if status is 'completed'
138
+ article.in_progress? # true if status is 'in_progress' or 'processing'
139
+ article.failed? # true if status is 'failed' or 'error'
140
+
141
+ # Convert to hash
142
+ article.to_h
143
+ ```
144
+
145
+ ### Generate Article Response
146
+
147
+ ```ruby
148
+ response = client.generate_article('keyword')
149
+
150
+ # Properties
151
+ response.message # Response message
152
+ response.success # Boolean success status
153
+ response.article_ids # Array of generated article IDs
154
+
155
+ # Methods
156
+ response.success? # true if generation was successful
157
+ response.has_article_ids? # true if article IDs are present
158
+ response.first_article_id # First article ID (convenience method)
159
+
160
+ # Convert to hash
161
+ response.to_h
162
+ ```
163
+
164
+ ## Error Handling
165
+
166
+ The SDK provides specific exception types for different error scenarios:
167
+
168
+ ```ruby
169
+ begin
170
+ response = client.generate_article('keyword')
171
+ rescue SemanticPen::ValidationError => e
172
+ puts "Validation Error: #{e.message}"
173
+ rescue SemanticPen::AuthenticationError => e
174
+ puts "Authentication Error: #{e.message}"
175
+ rescue SemanticPen::NotFoundError => e
176
+ puts "Not Found Error: #{e.message}"
177
+ rescue SemanticPen::RateLimitError => e
178
+ puts "Rate Limit Error: #{e.message}"
179
+ rescue SemanticPen::NetworkError => e
180
+ puts "Network Error: #{e.message}"
181
+ rescue SemanticPen::ApiError => e
182
+ puts "API Error: #{e.message} (HTTP #{e.http_status_code})"
183
+ rescue SemanticPen::Error => e
184
+ puts "SemanticPen Error: #{e.message}"
185
+ end
186
+ ```
187
+
188
+ ### Error Types
189
+
190
+ - `SemanticPen::ValidationError` - Invalid input parameters
191
+ - `SemanticPen::AuthenticationError` - Invalid API key or authentication issues
192
+ - `SemanticPen::NotFoundError` - Requested resource not found
193
+ - `SemanticPen::RateLimitError` - Rate limit exceeded
194
+ - `SemanticPen::NetworkError` - Network connectivity issues
195
+ - `SemanticPen::ApiError` - General API errors
196
+ - `SemanticPen::Error` - Base error class
197
+
198
+ ## Advanced Usage
199
+
200
+ ### Polling for Completion
201
+
202
+ Use the built-in `StatusPoller` to wait for article completion:
203
+
204
+ ```ruby
205
+ response = client.generate_article('AI trends 2025')
206
+
207
+ if response.success? && response.has_article_ids?
208
+ article_id = response.first_article_id
209
+
210
+ # Poll for completion
211
+ poller = SemanticPen::Models::StatusPoller.new(
212
+ client,
213
+ article_id,
214
+ max_attempts: 60, # Maximum polling attempts
215
+ delay: 5 # Delay between attempts (seconds)
216
+ )
217
+
218
+ article = poller.wait_for_completion
219
+
220
+ if article.completed?
221
+ puts "Article ready: #{article.title}"
222
+ else
223
+ puts "Article failed to complete"
224
+ end
225
+ end
226
+ ```
227
+
228
+ ### Bulk Generation
229
+
230
+ Generate multiple articles efficiently:
231
+
232
+ ```ruby
233
+ keywords = ['AI in healthcare', 'machine learning', 'data science']
234
+ articles = []
235
+
236
+ keywords.each do |keyword|
237
+ begin
238
+ response = client.generate_article(keyword, project_name: 'Tech Blog')
239
+ articles << response.first_article_id if response.success?
240
+ sleep(1) # Rate limiting
241
+ rescue SemanticPen::Error => e
242
+ puts "Failed to generate article for #{keyword}: #{e.message}"
243
+ end
244
+ end
245
+
246
+ # Check status of all articles
247
+ articles.each do |article_id|
248
+ article = client.get_article(article_id)
249
+ puts "#{article.target_keyword}: #{article.status} (#{article.progress}%)"
250
+ end
251
+ ```
252
+
253
+ ## Examples
254
+
255
+ The `examples/` directory contains comprehensive usage examples:
256
+
257
+ - `basic_usage.rb` - Simple article generation and status checking
258
+ - `polling_example.rb` - Using StatusPoller for completion waiting
259
+ - `bulk_generation.rb` - Generating multiple articles efficiently
260
+ - `configuration_example.rb` - Various configuration options and error handling
261
+
262
+ To run an example:
263
+
264
+ ```bash
265
+ cd examples
266
+ ruby basic_usage.rb
267
+ ```
268
+
269
+ ## Requirements
270
+
271
+ - Ruby 2.6.0 or higher
272
+ - `faraday` gem for HTTP requests
273
+ - `json` gem for JSON parsing
274
+
275
+ ## Development
276
+
277
+ After checking out the repo, run:
278
+
279
+ ```bash
280
+ bundle install
281
+ ```
282
+
283
+ To run tests:
284
+
285
+ ```bash
286
+ bundle exec rspec
287
+ ```
288
+
289
+ To run RuboCop for code style:
290
+
291
+ ```bash
292
+ bundle exec rubocop
293
+ ```
294
+
295
+ ## Contributing
296
+
297
+ 1. Fork the repository
298
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
299
+ 3. Commit your changes (`git commit -am 'Add amazing feature'`)
300
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
301
+ 5. Open a Pull Request
302
+
303
+ ## Support
304
+
305
+ For support and questions, please visit [SemanticPen API Documentation](https://www.semanticpen.com/api-documentation).
306
+
307
+ ## License
308
+
309
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
310
+
311
+ ## Changelog
312
+
313
+ ### 1.0.0 (2025-01-19)
314
+
315
+ - Initial release
316
+ - Basic article generation and retrieval
317
+ - Comprehensive error handling
318
+ - Built-in status polling
319
+ - Full test suite and documentation
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/semantic_pen'
5
+
6
+ begin
7
+ # Initialize the client with your API key
8
+ client = SemanticPen::Client.new(api_key: 'your-api-key-here')
9
+
10
+ # Generate an article
11
+ puts 'Generating article...'
12
+ response = client.generate_article('AI in healthcare', project_name: 'Medical Blog')
13
+
14
+ if response.success? && response.has_article_ids?
15
+ article_id = response.first_article_id
16
+ puts "Article generation started. ID: #{article_id}"
17
+ puts "Message: #{response.message}"
18
+
19
+ # Check article status
20
+ puts "\nChecking article status..."
21
+ article = client.get_article(article_id)
22
+ puts "Status: #{article.status} (#{article.progress}% complete)"
23
+ puts "Target Keyword: #{article.target_keyword}"
24
+ puts "Project: #{article.project_name}"
25
+
26
+ if article.completed?
27
+ puts "\nArticle completed!"
28
+ puts "Title: #{article.title}" if article.title
29
+ puts "Content preview: #{article.content[0..200]}..." if article.content
30
+ elsif article.in_progress?
31
+ puts "\nArticle is still being generated..."
32
+ elsif article.failed?
33
+ puts "\nArticle generation failed."
34
+ end
35
+ else
36
+ puts "Failed to generate article: #{response.message}"
37
+ end
38
+
39
+ rescue SemanticPen::ValidationError => e
40
+ puts "Validation Error: #{e.message}"
41
+ rescue SemanticPen::AuthenticationError => e
42
+ puts "Authentication Error: #{e.message}"
43
+ rescue SemanticPen::NotFoundError => e
44
+ puts "Not Found Error: #{e.message}"
45
+ rescue SemanticPen::NetworkError => e
46
+ puts "Network Error: #{e.message}"
47
+ rescue SemanticPen::ApiError => e
48
+ puts "API Error: #{e.message} (HTTP #{e.http_status_code})"
49
+ rescue SemanticPen::Error => e
50
+ puts "SemanticPen Error: #{e.message}"
51
+ rescue StandardError => e
52
+ puts "Unexpected Error: #{e.message}"
53
+ end
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/semantic_pen'
5
+
6
+ begin
7
+ # Initialize the client
8
+ client = SemanticPen::Client.new(api_key: 'your-api-key-here')
9
+
10
+ # Define keywords to generate articles for
11
+ keywords = [
12
+ 'artificial intelligence trends 2025',
13
+ 'sustainable energy solutions',
14
+ 'remote work productivity tips',
15
+ 'cybersecurity best practices',
16
+ 'blockchain technology applications'
17
+ ]
18
+
19
+ project_name = 'Tech Blog Batch'
20
+ generated_articles = []
21
+
22
+ puts "Starting bulk generation for #{keywords.length} articles..."
23
+
24
+ # Generate all articles
25
+ keywords.each_with_index do |keyword, index|
26
+ puts "\n#{index + 1}/#{keywords.length}: Generating article for '#{keyword}'"
27
+
28
+ begin
29
+ response = client.generate_article(keyword, project_name: project_name)
30
+
31
+ if response.success? && response.has_article_ids?
32
+ article_id = response.first_article_id
33
+ generated_articles << { keyword: keyword, id: article_id }
34
+ puts "✅ Started generation (ID: #{article_id})"
35
+ else
36
+ puts "❌ Failed: #{response.message}"
37
+ end
38
+ rescue SemanticPen::Error => e
39
+ puts "❌ Error generating article for '#{keyword}': #{e.message}"
40
+ end
41
+
42
+ # Small delay to avoid hitting rate limits
43
+ sleep(1) unless index == keywords.length - 1
44
+ end
45
+
46
+ if generated_articles.empty?
47
+ puts "\nNo articles were successfully started."
48
+ exit
49
+ end
50
+
51
+ puts "\n#{generated_articles.length} articles started. Checking status..."
52
+
53
+ # Check status of all generated articles
54
+ generated_articles.each do |article_info|
55
+ begin
56
+ article = client.get_article(article_info[:id])
57
+ status_emoji = case article.status
58
+ when 'completed' then '✅'
59
+ when 'failed', 'error' then '❌'
60
+ else '⏳'
61
+ end
62
+
63
+ puts "#{status_emoji} #{article_info[:keyword]}: #{article.status} (#{article.progress}%)"
64
+ rescue SemanticPen::Error => e
65
+ puts "❌ Error checking #{article_info[:keyword]}: #{e.message}"
66
+ end
67
+ end
68
+
69
+ # Wait for completion of all articles (simplified polling)
70
+ puts "\nWaiting for articles to complete..."
71
+ completed_count = 0
72
+ max_wait_minutes = 10
73
+
74
+ (1..max_wait_minutes).each do |minute|
75
+ puts "\nChecking progress (minute #{minute}/#{max_wait_minutes})..."
76
+ completed_count = 0
77
+
78
+ generated_articles.each do |article_info|
79
+ begin
80
+ article = client.get_article(article_info[:id])
81
+ completed_count += 1 if article.completed?
82
+ rescue SemanticPen::Error
83
+ # Article might still be processing
84
+ end
85
+ end
86
+
87
+ puts "#{completed_count}/#{generated_articles.length} articles completed"
88
+
89
+ break if completed_count == generated_articles.length
90
+
91
+ sleep(60) # Wait 1 minute before checking again
92
+ end
93
+
94
+ puts "\nFinal summary:"
95
+ puts "#{completed_count}/#{generated_articles.length} articles completed"
96
+
97
+ rescue SemanticPen::Error => e
98
+ puts "SemanticPen Error: #{e.message}"
99
+ rescue StandardError => e
100
+ puts "Unexpected Error: #{e.message}"
101
+ end
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/semantic_pen'
5
+
6
+ begin
7
+ # Example 1: Basic configuration
8
+ puts "=== Basic Configuration ==="
9
+ client = SemanticPen::Client.new(api_key: 'your-api-key-here')
10
+ puts "Client initialized with default settings"
11
+
12
+ # Example 2: Custom configuration
13
+ puts "\n=== Custom Configuration ==="
14
+ custom_client = SemanticPen::Client.new(
15
+ api_key: 'your-api-key-here',
16
+ base_url: 'https://api.semanticpen.com', # Custom base URL
17
+ timeout: 120 # 2 minute timeout
18
+ )
19
+ puts "Client initialized with custom base URL and timeout"
20
+
21
+ # Example 3: Global configuration (alternative approach)
22
+ puts "\n=== Global Configuration ==="
23
+ SemanticPen.configure do |config|
24
+ config.api_key = 'your-api-key-here'
25
+ config.base_url = 'https://www.semanticpen.com'
26
+ config.timeout = 90
27
+ end
28
+ puts "Global configuration set"
29
+
30
+ # Example 4: Environment-based configuration
31
+ puts "\n=== Environment-based Configuration ==="
32
+ api_key = ENV['SEMANTIC_PEN_API_KEY'] || 'your-api-key-here'
33
+ base_url = ENV['SEMANTIC_PEN_BASE_URL'] || 'https://www.semanticpen.com'
34
+
35
+ env_client = SemanticPen::Client.new(
36
+ api_key: api_key,
37
+ base_url: base_url
38
+ )
39
+ puts "Client configured from environment variables"
40
+
41
+ # Example 5: Comprehensive error handling
42
+ puts "\n=== Error Handling Examples ==="
43
+
44
+ # Test with invalid API key
45
+ begin
46
+ invalid_client = SemanticPen::Client.new(api_key: 'invalid-key')
47
+ invalid_client.generate_article('test keyword')
48
+ rescue SemanticPen::AuthenticationError => e
49
+ puts "✅ Caught authentication error: #{e.message}"
50
+ end
51
+
52
+ # Test with empty keyword
53
+ begin
54
+ client.generate_article('')
55
+ rescue SemanticPen::ValidationError => e
56
+ puts "✅ Caught validation error: #{e.message}"
57
+ end
58
+
59
+ # Test with invalid article ID
60
+ begin
61
+ client.get_article('non-existent-id')
62
+ rescue SemanticPen::NotFoundError => e
63
+ puts "✅ Caught not found error: #{e.message}"
64
+ end
65
+
66
+ # Example 6: Working with responses
67
+ puts "\n=== Response Handling ==="
68
+
69
+ # This would work with a valid API key
70
+ # response = client.generate_article('ruby programming')
71
+ #
72
+ # puts "Response success: #{response.success?}"
73
+ # puts "Message: #{response.message}"
74
+ # puts "Article IDs: #{response.article_ids}"
75
+ # puts "Has article IDs: #{response.has_article_ids?}"
76
+ # puts "First article ID: #{response.first_article_id}" if response.has_article_ids?
77
+
78
+ puts "\n=== Article Model Examples ==="
79
+
80
+ # Example article data structure
81
+ sample_article_data = {
82
+ 'id' => 'article-123',
83
+ 'title' => 'Introduction to Ruby Programming',
84
+ 'content' => 'Ruby is a dynamic, open source programming language...',
85
+ 'status' => 'completed',
86
+ 'progress' => 100,
87
+ 'target_keyword' => 'ruby programming',
88
+ 'project_name' => 'Tech Blog',
89
+ 'created_at' => '2025-01-01T10:00:00Z',
90
+ 'updated_at' => '2025-01-01T10:30:00Z'
91
+ }
92
+
93
+ article = SemanticPen::Models::Article.new(sample_article_data)
94
+
95
+ puts "Article ID: #{article.id}"
96
+ puts "Title: #{article.title}"
97
+ puts "Status: #{article.status}"
98
+ puts "Progress: #{article.progress}%"
99
+ puts "Is completed: #{article.completed?}"
100
+ puts "Is in progress: #{article.in_progress?}"
101
+ puts "Is failed: #{article.failed?}"
102
+ puts "Article hash: #{article.to_h}"
103
+
104
+ rescue SemanticPen::Error => e
105
+ puts "SemanticPen Error: #{e.message}"
106
+ puts "Error Code: #{e.error_code}" if e.error_code
107
+ puts "HTTP Status: #{e.http_status_code}" if e.http_status_code
108
+ rescue StandardError => e
109
+ puts "Unexpected Error: #{e.message}"
110
+ puts e.backtrace.first(5).join("\n")
111
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/semantic_pen'
5
+
6
+ begin
7
+ # Initialize the client
8
+ client = SemanticPen::Client.new(api_key: 'your-api-key-here')
9
+
10
+ # Generate an article
11
+ puts 'Generating article: "Machine Learning Basics"'
12
+ response = client.generate_article('machine learning basics', project_name: 'Tech Blog')
13
+
14
+ if response.success? && response.has_article_ids?
15
+ article_id = response.first_article_id
16
+ puts "Article generation started. ID: #{article_id}"
17
+
18
+ # Poll for completion using the StatusPoller utility
19
+ puts "\nWaiting for article to complete (polling every 5 seconds)..."
20
+ poller = SemanticPen::Models::StatusPoller.new(client, article_id, max_attempts: 30, delay: 5)
21
+
22
+ article = poller.wait_for_completion
23
+
24
+ if article.completed?
25
+ puts "\n✅ Article completed successfully!"
26
+ puts "Title: #{article.title}"
27
+ puts "Status: #{article.status}"
28
+ puts "Progress: #{article.progress}%"
29
+ puts "Created at: #{article.created_at}"
30
+ puts "Updated at: #{article.updated_at}"
31
+
32
+ if article.content
33
+ puts "\nContent preview:"
34
+ puts article.content[0..500] + (article.content.length > 500 ? '...' : '')
35
+ end
36
+ else
37
+ puts "\n❌ Article generation failed"
38
+ puts "Status: #{article.status}"
39
+ end
40
+ else
41
+ puts "Failed to start article generation: #{response.message}"
42
+ end
43
+
44
+ rescue SemanticPen::Error => e
45
+ puts "SemanticPen Error: #{e.message}"
46
+ puts "Error Code: #{e.error_code}" if e.error_code
47
+ puts "HTTP Status: #{e.http_status_code}" if e.http_status_code
48
+ rescue StandardError => e
49
+ puts "Unexpected Error: #{e.message}"
50
+ puts e.backtrace.join("\n")
51
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+
6
+ module SemanticPen
7
+ class Client
8
+ def initialize(api_key: nil, base_url: nil, timeout: nil)
9
+ @configuration = build_configuration(api_key, base_url, timeout)
10
+ @configuration.validate!
11
+ @connection = build_connection
12
+ end
13
+
14
+ def generate_article(target_keyword, project_name: nil)
15
+ raise ValidationError, 'Target keyword cannot be nil or empty' if target_keyword.nil? || target_keyword.strip.empty?
16
+
17
+ request_body = {
18
+ target_keyword: target_keyword.strip,
19
+ project_name: project_name
20
+ }.compact
21
+
22
+ response = post('/api/articles', request_body)
23
+ Models::GenerateArticleResponse.new(response)
24
+ rescue Faraday::Error => e
25
+ raise NetworkError.new("Failed to send request: #{e.message}", original_error: e)
26
+ end
27
+
28
+ def get_article(article_id)
29
+ raise ValidationError, 'Article ID cannot be nil or empty' if article_id.nil? || article_id.strip.empty?
30
+
31
+ response = get("/api/articles/#{article_id.strip}")
32
+ Models::Article.new(response)
33
+ rescue Faraday::Error => e
34
+ raise NetworkError.new("Failed to send request: #{e.message}", original_error: e)
35
+ end
36
+
37
+ private
38
+
39
+ def build_configuration(api_key, base_url, timeout)
40
+ config = Configuration.new
41
+ config.api_key = api_key
42
+ config.base_url = base_url if base_url
43
+ config.timeout = timeout if timeout
44
+ config
45
+ end
46
+
47
+ def build_connection
48
+ Faraday.new(url: @configuration.base_url) do |f|
49
+ f.request :json
50
+ f.response :json
51
+ f.request :authorization, 'Bearer', @configuration.api_key
52
+ f.headers['User-Agent'] = 'SemanticPen-Ruby-SDK/1.0.0'
53
+ f.adapter Faraday.default_adapter
54
+ f.options.timeout = @configuration.timeout
55
+ end
56
+ end
57
+
58
+ def get(path)
59
+ handle_response(@connection.get(path))
60
+ end
61
+
62
+ def post(path, body)
63
+ handle_response(@connection.post(path, body))
64
+ end
65
+
66
+ def handle_response(response)
67
+ case response.status
68
+ when 200..299
69
+ response.body
70
+ when 401
71
+ raise AuthenticationError, extract_error_message(response)
72
+ when 404
73
+ raise NotFoundError, extract_error_message(response)
74
+ when 429
75
+ raise RateLimitError, extract_error_message(response)
76
+ else
77
+ raise ApiError.new(extract_error_message(response), http_status_code: response.status)
78
+ end
79
+ end
80
+
81
+ def extract_error_message(response)
82
+ return response.body if response.body.is_a?(String)
83
+
84
+ response.body&.dig('message') || response.body&.dig('error') || 'Unknown error occurred'
85
+ rescue StandardError
86
+ 'Unknown error occurred'
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SemanticPen
4
+ class Configuration
5
+ attr_accessor :api_key, :base_url, :timeout
6
+
7
+ def initialize
8
+ @base_url = 'https://www.semanticpen.com'
9
+ @timeout = 60
10
+ end
11
+
12
+ def validate!
13
+ raise ArgumentError, 'API key is required' if api_key.nil? || api_key.empty?
14
+ raise ArgumentError, 'Base URL is required' if base_url.nil? || base_url.empty?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SemanticPen
4
+ class Error < StandardError
5
+ attr_reader :error_code, :http_status_code
6
+
7
+ def initialize(message, error_code: nil, http_status_code: nil)
8
+ super(message)
9
+ @error_code = error_code
10
+ @http_status_code = http_status_code
11
+ end
12
+ end
13
+
14
+ class NetworkError < Error
15
+ def initialize(message, original_error: nil)
16
+ super("Network error: #{message}", error_code: 'NETWORK_ERROR')
17
+ @original_error = original_error
18
+ end
19
+ end
20
+
21
+ class ValidationError < Error
22
+ def initialize(message)
23
+ super("Validation error: #{message}", error_code: 'VALIDATION_ERROR')
24
+ end
25
+ end
26
+
27
+ class AuthenticationError < Error
28
+ def initialize(message)
29
+ super("Authentication error: #{message}", error_code: 'AUTHENTICATION_ERROR', http_status_code: 401)
30
+ end
31
+ end
32
+
33
+ class NotFoundError < Error
34
+ def initialize(message)
35
+ super("Not found: #{message}", error_code: 'NOT_FOUND_ERROR', http_status_code: 404)
36
+ end
37
+ end
38
+
39
+ class ApiError < Error
40
+ def initialize(message, http_status_code: nil)
41
+ super("API error: #{message}", error_code: 'API_ERROR', http_status_code: http_status_code)
42
+ end
43
+ end
44
+
45
+ class RateLimitError < Error
46
+ def initialize(message)
47
+ super("Rate limit exceeded: #{message}", error_code: 'RATE_LIMIT_ERROR', http_status_code: 429)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SemanticPen
4
+ module Models
5
+ class Article
6
+ attr_reader :id, :title, :content, :status, :progress, :target_keyword, :project_name, :created_at, :updated_at
7
+
8
+ def initialize(data)
9
+ @id = data['id']
10
+ @title = data['title']
11
+ @content = data['content']
12
+ @status = data['status']
13
+ @progress = data['progress']
14
+ @target_keyword = data['target_keyword']
15
+ @project_name = data['project_name']
16
+ @created_at = data['created_at']
17
+ @updated_at = data['updated_at']
18
+ end
19
+
20
+ def completed?
21
+ status == 'completed'
22
+ end
23
+
24
+ def in_progress?
25
+ status == 'in_progress' || status == 'processing'
26
+ end
27
+
28
+ def failed?
29
+ status == 'failed' || status == 'error'
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ id: id,
35
+ title: title,
36
+ content: content,
37
+ status: status,
38
+ progress: progress,
39
+ target_keyword: target_keyword,
40
+ project_name: project_name,
41
+ created_at: created_at,
42
+ updated_at: updated_at
43
+ }
44
+ end
45
+ end
46
+
47
+ class GenerateArticleResponse
48
+ attr_reader :message, :success, :article_ids
49
+
50
+ def initialize(data)
51
+ @message = data['message']
52
+ @success = data['success']
53
+ @article_ids = data['article_ids'] || []
54
+ end
55
+
56
+ def success?
57
+ success == true
58
+ end
59
+
60
+ def has_article_ids?
61
+ !article_ids.empty?
62
+ end
63
+
64
+ def first_article_id
65
+ article_ids.first
66
+ end
67
+
68
+ def to_h
69
+ {
70
+ message: message,
71
+ success: success,
72
+ article_ids: article_ids
73
+ }
74
+ end
75
+ end
76
+
77
+ class GenerateArticleRequest
78
+ attr_reader :target_keyword, :project_name
79
+
80
+ def initialize(target_keyword, project_name = nil)
81
+ @target_keyword = target_keyword
82
+ @project_name = project_name
83
+ end
84
+
85
+ def to_h
86
+ {
87
+ target_keyword: target_keyword,
88
+ project_name: project_name
89
+ }.compact
90
+ end
91
+
92
+ def to_json(*args)
93
+ to_h.to_json(*args)
94
+ end
95
+ end
96
+
97
+ class StatusPoller
98
+ def initialize(client, article_id, max_attempts: 60, delay: 5)
99
+ @client = client
100
+ @article_id = article_id
101
+ @max_attempts = max_attempts
102
+ @delay = delay
103
+ end
104
+
105
+ def wait_for_completion
106
+ attempts = 0
107
+
108
+ loop do
109
+ article = @client.get_article(@article_id)
110
+
111
+ return article if article.completed? || article.failed?
112
+
113
+ attempts += 1
114
+ if attempts >= @max_attempts
115
+ raise Error, "Article generation timed out after #{@max_attempts} attempts"
116
+ end
117
+
118
+ sleep(@delay)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SemanticPen
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'semantic_pen/version'
4
+ require_relative 'semantic_pen/configuration'
5
+ require_relative 'semantic_pen/client'
6
+ require_relative 'semantic_pen/errors'
7
+ require_relative 'semantic_pen/models'
8
+
9
+ module SemanticPen
10
+ class << self
11
+ attr_accessor :configuration
12
+
13
+ def configure
14
+ self.configuration ||= Configuration.new
15
+ yield(configuration) if block_given?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/semantic_pen/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'semantic_pen'
7
+ spec.version = SemanticPen::VERSION
8
+ spec.authors = ['SemanticPen Team']
9
+ spec.email = ['support@semanticpen.com']
10
+
11
+ spec.summary = 'Official Ruby SDK for SemanticPen API - AI-powered content generation'
12
+ spec.description = 'A Ruby SDK for SemanticPen API that provides AI-powered content generation capabilities with a simple and intuitive interface.'
13
+ spec.homepage = 'https://www.semanticpen.com'
14
+ spec.license = 'MIT'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/pushkarsingh32/semanticpen-ruby-sdk'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/pushkarsingh32/semanticpen-ruby-sdk/blob/main/CHANGELOG.md'
19
+
20
+ spec.files = Dir['lib/**/*', 'examples/**/*', 'README.md', 'LICENSE', 'semantic_pen.gemspec']
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.required_ruby_version = '>= 2.6.0'
26
+
27
+ spec.add_dependency 'faraday', '~> 2.0'
28
+ spec.add_dependency 'json', '~> 2.0'
29
+
30
+ spec.add_development_dependency 'rspec', '~> 3.0'
31
+ spec.add_development_dependency 'rubocop', '~> 1.0'
32
+ spec.add_development_dependency 'webmock', '~> 3.0'
33
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: semantic_pen
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - SemanticPen Team
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: A Ruby SDK for SemanticPen API that provides AI-powered content generation
84
+ capabilities with a simple and intuitive interface.
85
+ email:
86
+ - support@semanticpen.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - LICENSE
92
+ - README.md
93
+ - examples/basic_usage.rb
94
+ - examples/bulk_generation.rb
95
+ - examples/configuration_example.rb
96
+ - examples/polling_example.rb
97
+ - lib/semantic_pen.rb
98
+ - lib/semantic_pen/client.rb
99
+ - lib/semantic_pen/configuration.rb
100
+ - lib/semantic_pen/errors.rb
101
+ - lib/semantic_pen/models.rb
102
+ - lib/semantic_pen/version.rb
103
+ - semantic_pen.gemspec
104
+ homepage: https://www.semanticpen.com
105
+ licenses:
106
+ - MIT
107
+ metadata:
108
+ homepage_uri: https://www.semanticpen.com
109
+ source_code_uri: https://github.com/pushkarsingh32/semanticpen-ruby-sdk
110
+ changelog_uri: https://github.com/pushkarsingh32/semanticpen-ruby-sdk/blob/main/CHANGELOG.md
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: 2.6.0
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubygems_version: 3.0.3.1
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Official Ruby SDK for SemanticPen API - AI-powered content generation
130
+ test_files: []