renamed 0.1.0.beta.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 77538a9c46e4722a2be7b056217e87e168464c54317be54c8139cbdf0d5f7a6e
4
+ data.tar.gz: 03b4a3515b130d8d8eed9b47f2d7a2c083c4c9e05388051432ca3748c90d4abe
5
+ SHA512:
6
+ metadata.gz: 3e1cce89fe45835b1eff235248236cf7229a86cb0f10d79fedd758670a6e5a352476a518baf498fac4cad04e2c93af0be809fecbe9ccdcd386f2355e0c01c06c
7
+ data.tar.gz: 32ce22cb167093c9770cd6ff79aa3d96701bd3099ff060d445189877f534a0991ba1324e9b2d3d1803618c7e532a2f8a376018d76fdc07c0aec61791ade83611
data/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # Renamed Ruby SDK
2
+
3
+ Official Ruby SDK for the [renamed.to](https://renamed.to) API. Rename files intelligently using AI, split PDFs, and extract structured data from documents.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'renamed'
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 renamed
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```ruby
28
+ require 'renamed'
29
+
30
+ # Initialize the client with your API key
31
+ client = Renamed::Client.new(api_key: 'rt_your_api_key')
32
+
33
+ # Rename a file using AI
34
+ result = client.rename('invoice.pdf')
35
+ puts result.suggested_filename # => "2025-01-15_AcmeCorp_INV-12345.pdf"
36
+ puts result.folder_path # => "Invoices/2025/January"
37
+ ```
38
+
39
+ ## Features
40
+
41
+ ### Rename Files
42
+
43
+ Intelligently rename files using AI analysis:
44
+
45
+ ```ruby
46
+ # Basic rename
47
+ result = client.rename('document.pdf')
48
+ puts result.suggested_filename
49
+ puts result.folder_path
50
+ puts result.confidence
51
+
52
+ # With custom template
53
+ result = client.rename('invoice.pdf', template: '{date}_{vendor}_{type}')
54
+ puts result.suggested_filename
55
+ ```
56
+
57
+ ### Split PDFs
58
+
59
+ Split multi-page PDFs into separate documents:
60
+
61
+ ```ruby
62
+ # Auto-detect document boundaries
63
+ job = client.pdf_split('multi-page.pdf', mode: 'auto')
64
+
65
+ # Wait for completion with progress updates
66
+ result = job.wait do |status|
67
+ puts "Progress: #{status.progress}%"
68
+ end
69
+
70
+ # Download the split documents
71
+ result.documents.each do |doc|
72
+ puts "#{doc.filename} (pages #{doc.pages})"
73
+ content = client.download_file(doc.download_url)
74
+ File.binwrite(doc.filename, content)
75
+ end
76
+
77
+ # Split by fixed page count
78
+ job = client.pdf_split('document.pdf', mode: 'pages', pages_per_split: 5)
79
+ result = job.wait
80
+
81
+ # Split at blank pages
82
+ job = client.pdf_split('scanned.pdf', mode: 'blank')
83
+ result = job.wait
84
+ ```
85
+
86
+ ### Extract Data
87
+
88
+ Extract structured data from documents:
89
+
90
+ ```ruby
91
+ # Extract with natural language prompt
92
+ result = client.extract('invoice.pdf', prompt: 'Extract the vendor name, invoice number, and total amount')
93
+ puts result.data
94
+ # => {"vendor" => "Acme Corp", "invoice_number" => "INV-12345", "total" => 1250.00}
95
+
96
+ # Extract with JSON schema
97
+ schema = {
98
+ type: 'object',
99
+ properties: {
100
+ vendor: { type: 'string' },
101
+ invoice_number: { type: 'string' },
102
+ line_items: {
103
+ type: 'array',
104
+ items: {
105
+ type: 'object',
106
+ properties: {
107
+ description: { type: 'string' },
108
+ quantity: { type: 'number' },
109
+ price: { type: 'number' }
110
+ }
111
+ }
112
+ },
113
+ total: { type: 'number' }
114
+ }
115
+ }
116
+
117
+ result = client.extract('invoice.pdf', schema: schema)
118
+ puts result.data
119
+ puts result.confidence
120
+ ```
121
+
122
+ ### Check User Credits
123
+
124
+ ```ruby
125
+ user = client.get_user
126
+ puts "Email: #{user.email}"
127
+ puts "Credits remaining: #{user.credits}"
128
+ puts "Team: #{user.team&.name}"
129
+ ```
130
+
131
+ ## Configuration
132
+
133
+ ```ruby
134
+ client = Renamed::Client.new(
135
+ api_key: 'rt_your_api_key',
136
+ base_url: 'https://www.renamed.to/api/v1', # Default
137
+ timeout: 30, # Request timeout in seconds
138
+ max_retries: 2 # Number of retries for failed requests
139
+ )
140
+ ```
141
+
142
+ ## Error Handling
143
+
144
+ The SDK provides specific error classes for different error conditions:
145
+
146
+ ```ruby
147
+ begin
148
+ result = client.rename('document.pdf')
149
+ rescue Renamed::AuthenticationError => e
150
+ puts "Invalid API key: #{e.message}"
151
+ rescue Renamed::InsufficientCreditsError => e
152
+ puts "Not enough credits: #{e.message}"
153
+ rescue Renamed::RateLimitError => e
154
+ puts "Rate limited. Retry after: #{e.retry_after} seconds"
155
+ rescue Renamed::ValidationError => e
156
+ puts "Invalid request: #{e.message}"
157
+ puts "Details: #{e.details}"
158
+ rescue Renamed::NetworkError => e
159
+ puts "Network error: #{e.message}"
160
+ rescue Renamed::TimeoutError => e
161
+ puts "Request timed out: #{e.message}"
162
+ rescue Renamed::JobError => e
163
+ puts "Job failed: #{e.message}"
164
+ puts "Job ID: #{e.job_id}"
165
+ rescue Renamed::Error => e
166
+ puts "General error: #{e.message}"
167
+ puts "Code: #{e.code}"
168
+ puts "Status: #{e.status_code}"
169
+ end
170
+ ```
171
+
172
+ ## File Input Types
173
+
174
+ All file methods accept multiple input types:
175
+
176
+ ```ruby
177
+ # String path
178
+ result = client.rename('/path/to/document.pdf')
179
+
180
+ # File object
181
+ File.open('document.pdf', 'rb') do |file|
182
+ result = client.rename(file)
183
+ end
184
+
185
+ # IO object
186
+ io = StringIO.new(pdf_content)
187
+ result = client.rename(io)
188
+ ```
189
+
190
+ ## Supported File Types
191
+
192
+ - PDF (`.pdf`)
193
+ - JPEG (`.jpg`, `.jpeg`)
194
+ - PNG (`.png`)
195
+ - TIFF (`.tif`, `.tiff`)
196
+
197
+ ## Development
198
+
199
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
200
+
201
+ ```bash
202
+ bundle install
203
+ bundle exec rake test
204
+ ```
205
+
206
+ To generate documentation:
207
+
208
+ ```bash
209
+ bundle exec yard doc
210
+ ```
211
+
212
+ ## Contributing
213
+
214
+ Bug reports and pull requests are welcome on GitHub at https://github.com/renamed-to/renamed-sdk.
215
+
216
+ ## License
217
+
218
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "models"
4
+ require_relative "errors"
5
+
6
+ module Renamed
7
+ # Async job handle for long-running operations like PDF split.
8
+ class AsyncJob
9
+ DEFAULT_POLL_INTERVAL = 2.0
10
+ MAX_POLL_ATTEMPTS = 150 # 5 minutes at 2s intervals
11
+
12
+ attr_reader :status_url
13
+
14
+ # @param client [Client] The Renamed client instance
15
+ # @param status_url [String] URL to poll for job status
16
+ # @param poll_interval [Float] Interval between status checks in seconds
17
+ # @param max_attempts [Integer] Maximum number of polling attempts
18
+ def initialize(client, status_url, poll_interval: DEFAULT_POLL_INTERVAL, max_attempts: MAX_POLL_ATTEMPTS)
19
+ @client = client
20
+ @status_url = status_url
21
+ @poll_interval = poll_interval
22
+ @max_attempts = max_attempts
23
+ end
24
+
25
+ # Get current job status.
26
+ #
27
+ # @return [JobStatusResponse] Current job status
28
+ def status
29
+ response = @client.send(:request, :get, @status_url)
30
+ JobStatusResponse.from_response(response)
31
+ end
32
+
33
+ # Wait for job completion, polling at regular intervals.
34
+ #
35
+ # @yield [JobStatusResponse] Called with status updates during polling
36
+ # @return [PdfSplitResult] The completed job result
37
+ # @raise [JobError] If the job fails or times out
38
+ #
39
+ # @example Wait for job completion
40
+ # job = client.pdf_split("multi-page.pdf", mode: "auto")
41
+ # result = job.wait { |status| puts "Progress: #{status.progress}%" }
42
+ # result.documents.each do |doc|
43
+ # puts "#{doc.filename}: #{doc.download_url}"
44
+ # end
45
+ def wait
46
+ attempts = 0
47
+
48
+ while attempts < @max_attempts
49
+ current_status = status
50
+
51
+ yield current_status if block_given?
52
+
53
+ if current_status.status == "completed" && current_status.result
54
+ return current_status.result
55
+ end
56
+
57
+ if current_status.status == "failed"
58
+ raise JobError.new(current_status.error || "Job failed", job_id: current_status.job_id)
59
+ end
60
+
61
+ attempts += 1
62
+ sleep(@poll_interval)
63
+ end
64
+
65
+ raise JobError.new("Job polling timeout exceeded")
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/multipart"
5
+ require "json"
6
+
7
+ require_relative "errors"
8
+ require_relative "models"
9
+ require_relative "async_job"
10
+
11
+ module Renamed
12
+ # renamed.to API client.
13
+ #
14
+ # @example Basic usage
15
+ # client = Renamed::Client.new(api_key: "rt_your_api_key")
16
+ # result = client.rename("invoice.pdf")
17
+ # puts result.suggested_filename
18
+ #
19
+ # @example With custom configuration
20
+ # client = Renamed::Client.new(
21
+ # api_key: "rt_your_api_key",
22
+ # timeout: 60,
23
+ # max_retries: 3
24
+ # )
25
+ class Client
26
+ DEFAULT_BASE_URL = "https://www.renamed.to/api/v1"
27
+ DEFAULT_TIMEOUT = 30
28
+ DEFAULT_MAX_RETRIES = 2
29
+
30
+ # @param api_key [String] API key for authentication (starts with rt_)
31
+ # @param base_url [String] Base URL for the API
32
+ # @param timeout [Integer] Request timeout in seconds
33
+ # @param max_retries [Integer] Maximum number of retries for failed requests
34
+ def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES)
35
+ raise AuthenticationError, "API key is required" if api_key.nil? || api_key.empty?
36
+
37
+ @api_key = api_key
38
+ @base_url = base_url.chomp("/")
39
+ @timeout = timeout
40
+ @max_retries = max_retries
41
+
42
+ @connection = build_connection
43
+ end
44
+
45
+ # Get current user profile and credits.
46
+ #
47
+ # @return [User] User profile with credits balance
48
+ #
49
+ # @example
50
+ # user = client.get_user
51
+ # puts "Credits remaining: #{user.credits}"
52
+ def get_user
53
+ response = request(:get, "/user")
54
+ User.from_response(response)
55
+ end
56
+
57
+ # Rename a file using AI.
58
+ #
59
+ # @param file [String, File, IO] File to rename (path, File object, or IO)
60
+ # @param options [Hash] Additional options
61
+ # @option options [String] :template Custom template for filename generation
62
+ #
63
+ # @return [RenameResult] Result with suggested filename and folder path
64
+ #
65
+ # @example Rename a file by path
66
+ # result = client.rename("invoice.pdf")
67
+ # puts result.suggested_filename # "2025-01-15_AcmeCorp_INV-12345.pdf"
68
+ #
69
+ # @example Rename with custom template
70
+ # result = client.rename("invoice.pdf", template: "{date}_{vendor}_{type}")
71
+ # puts result.suggested_filename
72
+ def rename(file, options: {})
73
+ additional_fields = {}
74
+ additional_fields["template"] = options[:template] if options[:template]
75
+
76
+ response = upload_file("/rename", file, additional_fields: additional_fields)
77
+ RenameResult.from_response(response)
78
+ end
79
+
80
+ # Split a PDF into multiple documents.
81
+ #
82
+ # Returns an AsyncJob that can be polled for completion.
83
+ #
84
+ # @param file [String, File, IO] PDF file to split
85
+ # @param options [Hash] Additional options
86
+ # @option options [String] :mode Split mode ('auto', 'pages', or 'blank')
87
+ # @option options [Integer] :pages_per_split Number of pages per split (for 'pages' mode)
88
+ #
89
+ # @return [AsyncJob] Job that can be waited on for the result
90
+ #
91
+ # @example Split a PDF with auto-detection
92
+ # job = client.pdf_split("multi-page.pdf", mode: "auto")
93
+ # result = job.wait { |s| puts "Progress: #{s.progress}%" }
94
+ # result.documents.each do |doc|
95
+ # puts "#{doc.filename}: #{doc.download_url}"
96
+ # end
97
+ #
98
+ # @example Split into fixed page chunks
99
+ # job = client.pdf_split("document.pdf", mode: "pages", pages_per_split: 5)
100
+ # result = job.wait
101
+ def pdf_split(file, options: {})
102
+ additional_fields = {}
103
+ additional_fields["mode"] = options[:mode] if options[:mode]
104
+ additional_fields["pagesPerSplit"] = options[:pages_per_split].to_s if options[:pages_per_split]
105
+
106
+ response = upload_file("/pdf-split", file, additional_fields: additional_fields)
107
+ AsyncJob.new(self, response["statusUrl"])
108
+ end
109
+
110
+ # Extract structured data from a document.
111
+ #
112
+ # @param file [String, File, IO] Document to extract data from
113
+ # @param options [Hash] Additional options
114
+ # @option options [String] :prompt Natural language description of what to extract
115
+ # @option options [Hash] :schema JSON schema defining the structure of data to extract
116
+ #
117
+ # @return [ExtractResult] Result with extracted data
118
+ #
119
+ # @example Extract with a prompt
120
+ # result = client.extract("invoice.pdf", prompt: "Extract invoice details")
121
+ # puts result.data
122
+ #
123
+ # @example Extract with a schema
124
+ # schema = {
125
+ # type: "object",
126
+ # properties: {
127
+ # vendor: { type: "string" },
128
+ # total: { type: "number" }
129
+ # }
130
+ # }
131
+ # result = client.extract("invoice.pdf", schema: schema)
132
+ def extract(file, options: {})
133
+ additional_fields = {}
134
+ additional_fields["prompt"] = options[:prompt] if options[:prompt]
135
+ additional_fields["schema"] = JSON.generate(options[:schema]) if options[:schema]
136
+
137
+ response = upload_file("/extract", file, additional_fields: additional_fields)
138
+ ExtractResult.from_response(response)
139
+ end
140
+
141
+ # Download a file from a URL (e.g., split document).
142
+ #
143
+ # @param url [String] URL to download from
144
+ # @return [String] File content as binary string
145
+ #
146
+ # @example Download split documents
147
+ # result = job.wait
148
+ # result.documents.each do |doc|
149
+ # content = client.download_file(doc.download_url)
150
+ # File.binwrite(doc.filename, content)
151
+ # end
152
+ def download_file(url)
153
+ response = @connection.get(url)
154
+ handle_download_response(response)
155
+ end
156
+
157
+ private
158
+
159
+ def build_connection
160
+ Faraday.new(url: @base_url) do |conn|
161
+ conn.request :multipart
162
+ conn.request :url_encoded
163
+ conn.headers["Authorization"] = "Bearer #{@api_key}"
164
+ conn.options.timeout = @timeout
165
+ conn.options.open_timeout = @timeout
166
+ conn.adapter Faraday.default_adapter
167
+ end
168
+ end
169
+
170
+ def build_url(path)
171
+ return path if path.start_with?("http://", "https://")
172
+
173
+ path = "/#{path}" unless path.start_with?("/")
174
+ "#{@base_url}#{path}"
175
+ end
176
+
177
+ def request(method, path, **options)
178
+ url = build_url(path)
179
+ last_error = nil
180
+ attempts = 0
181
+
182
+ while attempts <= @max_retries
183
+ begin
184
+ response = @connection.send(method, url, options[:body], options[:headers])
185
+ return handle_response(response)
186
+ rescue Faraday::ConnectionFailed => e
187
+ last_error = NetworkError.new(e.message)
188
+ rescue Faraday::TimeoutError => e
189
+ last_error = TimeoutError.new(e.message)
190
+ rescue Renamed::Error => e
191
+ # Don't retry client errors (4xx)
192
+ raise if e.status_code && e.status_code >= 400 && e.status_code < 500
193
+
194
+ last_error = e
195
+ end
196
+
197
+ attempts += 1
198
+ sleep(2**attempts * 0.1) if attempts <= @max_retries
199
+ end
200
+
201
+ raise last_error || NetworkError.new
202
+ end
203
+
204
+ def handle_response(response)
205
+ if response.status >= 400
206
+ payload = parse_response_body(response)
207
+ raise Renamed.error_from_http_status(response.status, response.reason_phrase || "Error", payload)
208
+ end
209
+
210
+ parse_response_body(response)
211
+ end
212
+
213
+ def handle_download_response(response)
214
+ if response.status >= 400
215
+ raise Renamed.error_from_http_status(response.status, response.reason_phrase || "Error")
216
+ end
217
+
218
+ response.body
219
+ end
220
+
221
+ def parse_response_body(response)
222
+ return {} if response.body.nil? || response.body.empty?
223
+
224
+ JSON.parse(response.body)
225
+ rescue JSON::ParserError
226
+ response.body
227
+ end
228
+
229
+ def upload_file(path, file, additional_fields: {})
230
+ filename, content, mime_type = prepare_file(file)
231
+
232
+ payload = {}
233
+ payload[:file] = Faraday::Multipart::FilePart.new(
234
+ StringIO.new(content),
235
+ mime_type,
236
+ filename
237
+ )
238
+
239
+ additional_fields.each do |key, value|
240
+ payload[key] = value
241
+ end
242
+
243
+ url = build_url(path)
244
+ last_error = nil
245
+ attempts = 0
246
+
247
+ while attempts <= @max_retries
248
+ begin
249
+ response = @connection.post(url, payload)
250
+ return handle_response(response)
251
+ rescue Faraday::ConnectionFailed => e
252
+ last_error = NetworkError.new(e.message)
253
+ rescue Faraday::TimeoutError => e
254
+ last_error = TimeoutError.new(e.message)
255
+ rescue Renamed::Error => e
256
+ raise if e.status_code && e.status_code >= 400 && e.status_code < 500
257
+
258
+ last_error = e
259
+ end
260
+
261
+ attempts += 1
262
+ sleep(2**attempts * 0.1) if attempts <= @max_retries
263
+ end
264
+
265
+ raise last_error || NetworkError.new
266
+ end
267
+
268
+ def prepare_file(file)
269
+ case file
270
+ when String
271
+ # File path
272
+ path = File.expand_path(file)
273
+ content = File.binread(path)
274
+ filename = File.basename(path)
275
+ [filename, content, get_mime_type(filename)]
276
+ when File, IO
277
+ content = file.read
278
+ filename = file.respond_to?(:path) && file.path ? File.basename(file.path) : "file"
279
+ [filename, content, get_mime_type(filename)]
280
+ else
281
+ raise ValidationError.new("Invalid file input: expected String path, File, or IO object")
282
+ end
283
+ end
284
+
285
+ def get_mime_type(filename)
286
+ ext = File.extname(filename).downcase
287
+ MIME_TYPES[ext] || "application/octet-stream"
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renamed
4
+ # Base exception for all renamed.to SDK errors.
5
+ class Error < StandardError
6
+ attr_reader :message, :code, :status_code, :details
7
+
8
+ def initialize(message = "An error occurred", code: "UNKNOWN_ERROR", status_code: nil, details: nil)
9
+ @message = message
10
+ @code = code
11
+ @status_code = status_code
12
+ @details = details
13
+ super(message)
14
+ end
15
+ end
16
+
17
+ # Authentication error - invalid or missing API key.
18
+ class AuthenticationError < Error
19
+ def initialize(message = "Invalid or missing API key")
20
+ super(message, code: "AUTHENTICATION_ERROR", status_code: 401)
21
+ end
22
+ end
23
+
24
+ # Insufficient credits error.
25
+ class InsufficientCreditsError < Error
26
+ def initialize(message = "Insufficient credits")
27
+ super(message, code: "INSUFFICIENT_CREDITS", status_code: 402)
28
+ end
29
+ end
30
+
31
+ # Rate limit exceeded.
32
+ class RateLimitError < Error
33
+ attr_reader :retry_after
34
+
35
+ def initialize(message = "Rate limit exceeded", retry_after: nil)
36
+ super(message, code: "RATE_LIMIT_ERROR", status_code: 429)
37
+ @retry_after = retry_after
38
+ end
39
+ end
40
+
41
+ # Validation error - invalid request parameters.
42
+ class ValidationError < Error
43
+ def initialize(message, details: nil)
44
+ super(message, code: "VALIDATION_ERROR", status_code: 400, details: details)
45
+ end
46
+ end
47
+
48
+ # Network error - connection failed.
49
+ class NetworkError < Error
50
+ def initialize(message = "Network request failed")
51
+ super(message, code: "NETWORK_ERROR")
52
+ end
53
+ end
54
+
55
+ # Timeout error - request took too long.
56
+ class TimeoutError < Error
57
+ def initialize(message = "Request timed out")
58
+ super(message, code: "TIMEOUT_ERROR")
59
+ end
60
+ end
61
+
62
+ # Job error - async job failed.
63
+ class JobError < Error
64
+ attr_reader :job_id
65
+
66
+ def initialize(message, job_id: nil)
67
+ super(message, code: "JOB_ERROR")
68
+ @job_id = job_id
69
+ end
70
+ end
71
+
72
+ # Create appropriate error from HTTP status code.
73
+ #
74
+ # @param status [Integer] HTTP status code
75
+ # @param status_text [String] HTTP status text
76
+ # @param payload [Hash, String, nil] Response payload
77
+ # @return [Error] Appropriate error instance
78
+ def self.error_from_http_status(status, status_text, payload = nil)
79
+ message = status_text
80
+ if payload.is_a?(Hash) && payload["error"]
81
+ message = payload["error"].to_s
82
+ end
83
+
84
+ case status
85
+ when 401
86
+ AuthenticationError.new(message)
87
+ when 402
88
+ InsufficientCreditsError.new(message)
89
+ when 400, 422
90
+ ValidationError.new(message, details: payload)
91
+ when 429
92
+ retry_after = payload.is_a?(Hash) ? payload["retryAfter"] : nil
93
+ RateLimitError.new(message, retry_after: retry_after)
94
+ else
95
+ Error.new(message, code: "API_ERROR", status_code: status, details: payload)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renamed
4
+ # Result of a rename operation.
5
+ class RenameResult
6
+ attr_reader :original_filename, :suggested_filename, :folder_path, :confidence
7
+
8
+ # @param original_filename [String] Original filename that was uploaded
9
+ # @param suggested_filename [String] AI-suggested new filename
10
+ # @param folder_path [String, nil] Suggested folder path for organization
11
+ # @param confidence [Float, nil] Confidence score (0-1) of the suggestion
12
+ def initialize(original_filename:, suggested_filename:, folder_path: nil, confidence: nil)
13
+ @original_filename = original_filename
14
+ @suggested_filename = suggested_filename
15
+ @folder_path = folder_path
16
+ @confidence = confidence
17
+ end
18
+
19
+ # Create instance from API response hash.
20
+ #
21
+ # @param data [Hash] API response data
22
+ # @return [RenameResult]
23
+ def self.from_response(data)
24
+ new(
25
+ original_filename: data["originalFilename"],
26
+ suggested_filename: data["suggestedFilename"],
27
+ folder_path: data["folderPath"],
28
+ confidence: data["confidence"]
29
+ )
30
+ end
31
+
32
+ def to_h
33
+ {
34
+ original_filename: @original_filename,
35
+ suggested_filename: @suggested_filename,
36
+ folder_path: @folder_path,
37
+ confidence: @confidence
38
+ }.compact
39
+ end
40
+ end
41
+
42
+ # A single document from PDF split.
43
+ class SplitDocument
44
+ attr_reader :index, :filename, :pages, :download_url, :size
45
+
46
+ # @param index [Integer] Document index (0-based)
47
+ # @param filename [String] Suggested filename for this document
48
+ # @param pages [String] Page range included in this document
49
+ # @param download_url [String] URL to download this document
50
+ # @param size [Integer] Size in bytes
51
+ def initialize(index:, filename:, pages:, download_url:, size:)
52
+ @index = index
53
+ @filename = filename
54
+ @pages = pages
55
+ @download_url = download_url
56
+ @size = size
57
+ end
58
+
59
+ # Create instance from API response hash.
60
+ #
61
+ # @param data [Hash] API response data
62
+ # @return [SplitDocument]
63
+ def self.from_response(data)
64
+ new(
65
+ index: data["index"],
66
+ filename: data["filename"],
67
+ pages: data["pages"],
68
+ download_url: data["downloadUrl"],
69
+ size: data["size"]
70
+ )
71
+ end
72
+
73
+ def to_h
74
+ {
75
+ index: @index,
76
+ filename: @filename,
77
+ pages: @pages,
78
+ download_url: @download_url,
79
+ size: @size
80
+ }
81
+ end
82
+ end
83
+
84
+ # Result of PDF split operation.
85
+ class PdfSplitResult
86
+ attr_reader :original_filename, :documents, :total_pages
87
+
88
+ # @param original_filename [String] Original filename
89
+ # @param documents [Array<SplitDocument>] Split documents
90
+ # @param total_pages [Integer] Total number of pages in original document
91
+ def initialize(original_filename:, documents:, total_pages:)
92
+ @original_filename = original_filename
93
+ @documents = documents
94
+ @total_pages = total_pages
95
+ end
96
+
97
+ # Create instance from API response hash.
98
+ #
99
+ # @param data [Hash] API response data
100
+ # @return [PdfSplitResult]
101
+ def self.from_response(data)
102
+ documents = (data["documents"] || []).map { |doc| SplitDocument.from_response(doc) }
103
+ new(
104
+ original_filename: data["originalFilename"],
105
+ documents: documents,
106
+ total_pages: data["totalPages"]
107
+ )
108
+ end
109
+
110
+ def to_h
111
+ {
112
+ original_filename: @original_filename,
113
+ documents: @documents.map(&:to_h),
114
+ total_pages: @total_pages
115
+ }
116
+ end
117
+ end
118
+
119
+ # Response from job status endpoint.
120
+ class JobStatusResponse
121
+ attr_reader :job_id, :status, :progress, :error, :result
122
+
123
+ # @param job_id [String] Unique job identifier
124
+ # @param status [String] Current job status (pending, processing, completed, failed)
125
+ # @param progress [Integer, nil] Progress percentage (0-100)
126
+ # @param error [String, nil] Error message if job failed
127
+ # @param result [PdfSplitResult, nil] Result data when job is completed
128
+ def initialize(job_id:, status:, progress: nil, error: nil, result: nil)
129
+ @job_id = job_id
130
+ @status = status
131
+ @progress = progress
132
+ @error = error
133
+ @result = result
134
+ end
135
+
136
+ # Create instance from API response hash.
137
+ #
138
+ # @param data [Hash] API response data
139
+ # @return [JobStatusResponse]
140
+ def self.from_response(data)
141
+ result = data["result"] ? PdfSplitResult.from_response(data["result"]) : nil
142
+ new(
143
+ job_id: data["jobId"],
144
+ status: data["status"],
145
+ progress: data["progress"],
146
+ error: data["error"],
147
+ result: result
148
+ )
149
+ end
150
+
151
+ def to_h
152
+ {
153
+ job_id: @job_id,
154
+ status: @status,
155
+ progress: @progress,
156
+ error: @error,
157
+ result: @result&.to_h
158
+ }.compact
159
+ end
160
+ end
161
+
162
+ # Result of extract operation.
163
+ class ExtractResult
164
+ attr_reader :data, :confidence
165
+
166
+ # @param data [Hash] Extracted data matching the schema
167
+ # @param confidence [Float] Confidence score (0-1)
168
+ def initialize(data:, confidence:)
169
+ @data = data
170
+ @confidence = confidence
171
+ end
172
+
173
+ # Create instance from API response hash.
174
+ #
175
+ # @param response_data [Hash] API response data
176
+ # @return [ExtractResult]
177
+ def self.from_response(response_data)
178
+ new(
179
+ data: response_data["data"],
180
+ confidence: response_data["confidence"]
181
+ )
182
+ end
183
+
184
+ def to_h
185
+ {
186
+ data: @data,
187
+ confidence: @confidence
188
+ }
189
+ end
190
+ end
191
+
192
+ # Team information.
193
+ class Team
194
+ attr_reader :id, :name
195
+
196
+ # @param id [String] Team ID
197
+ # @param name [String] Team name
198
+ def initialize(id:, name:)
199
+ @id = id
200
+ @name = name
201
+ end
202
+
203
+ # Create instance from API response hash.
204
+ #
205
+ # @param data [Hash] API response data
206
+ # @return [Team]
207
+ def self.from_response(data)
208
+ new(
209
+ id: data["id"],
210
+ name: data["name"]
211
+ )
212
+ end
213
+
214
+ def to_h
215
+ {
216
+ id: @id,
217
+ name: @name
218
+ }
219
+ end
220
+ end
221
+
222
+ # User profile information.
223
+ class User
224
+ attr_reader :id, :email, :name, :credits, :team
225
+
226
+ # @param id [String] User ID
227
+ # @param email [String] Email address
228
+ # @param name [String, nil] Display name
229
+ # @param credits [Integer, nil] Available credits
230
+ # @param team [Team, nil] Team information (if applicable)
231
+ def initialize(id:, email:, name: nil, credits: nil, team: nil)
232
+ @id = id
233
+ @email = email
234
+ @name = name
235
+ @credits = credits
236
+ @team = team
237
+ end
238
+
239
+ # Create instance from API response hash.
240
+ #
241
+ # @param data [Hash] API response data
242
+ # @return [User]
243
+ def self.from_response(data)
244
+ team = data["team"] ? Team.from_response(data["team"]) : nil
245
+ new(
246
+ id: data["id"],
247
+ email: data["email"],
248
+ name: data["name"],
249
+ credits: data["credits"],
250
+ team: team
251
+ )
252
+ end
253
+
254
+ def to_h
255
+ {
256
+ id: @id,
257
+ email: @email,
258
+ name: @name,
259
+ credits: @credits,
260
+ team: @team&.to_h
261
+ }.compact
262
+ end
263
+ end
264
+
265
+ # MIME types for supported file formats.
266
+ MIME_TYPES = {
267
+ ".pdf" => "application/pdf",
268
+ ".jpg" => "image/jpeg",
269
+ ".jpeg" => "image/jpeg",
270
+ ".png" => "image/png",
271
+ ".tiff" => "image/tiff",
272
+ ".tif" => "image/tiff"
273
+ }.freeze
274
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renamed
4
+ VERSION = "0.1.0.beta.2"
5
+ end
data/lib/renamed.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "renamed/version"
4
+ require_relative "renamed/errors"
5
+ require_relative "renamed/models"
6
+ require_relative "renamed/async_job"
7
+ require_relative "renamed/client"
8
+
9
+ # Official Ruby SDK for renamed.to API.
10
+ #
11
+ # @example Basic usage
12
+ # require "renamed"
13
+ #
14
+ # client = Renamed::Client.new(api_key: "rt_your_api_key")
15
+ #
16
+ # # Rename a file
17
+ # result = client.rename("invoice.pdf")
18
+ # puts result.suggested_filename
19
+ #
20
+ # # Check credits
21
+ # user = client.get_user
22
+ # puts "Credits: #{user.credits}"
23
+ #
24
+ # @see https://renamed.to/docs API Documentation
25
+ module Renamed
26
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: renamed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta.2
5
+ platform: ruby
6
+ authors:
7
+ - renamed.to
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-11 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
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: faraday-multipart
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
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.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '13.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rubocop
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: webmock
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
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'
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'
131
+ description: Ruby client library for the renamed.to API. Rename files intelligently
132
+ using AI, split PDFs, and extract structured data from documents.
133
+ email:
134
+ - support@renamed.to
135
+ executables: []
136
+ extensions: []
137
+ extra_rdoc_files: []
138
+ files:
139
+ - README.md
140
+ - lib/renamed.rb
141
+ - lib/renamed/async_job.rb
142
+ - lib/renamed/client.rb
143
+ - lib/renamed/errors.rb
144
+ - lib/renamed/models.rb
145
+ - lib/renamed/version.rb
146
+ homepage: https://renamed.to
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ homepage_uri: https://renamed.to
151
+ source_code_uri: https://github.com/renamed-to/renamed-sdk
152
+ changelog_uri: https://github.com/renamed-to/renamed-sdk/blob/main/sdks/ruby/CHANGELOG.md
153
+ documentation_uri: https://renamed.to/docs
154
+ rubygems_mfa_required: 'true'
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 3.0.0
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubygems_version: 3.5.22
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Official Ruby SDK for renamed.to API
174
+ test_files: []