konfidant 0.7.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: d76590436720d9998f06a04fffa4a5a06b3aff597c675e6b4a1eafe770cd383f
4
+ data.tar.gz: 2e09ccdb885e047fdefe69faeba3289a919052534d07ef0bc5040a0e91256b14
5
+ SHA512:
6
+ metadata.gz: 6e858d0172471e5e88261696bc2201307e582f4003eb588ee6308fb0c1e2d280c72e6673b5329c00b2efc64b204a4abb9adacd848700d6a2daaeb04fe491a5c1
7
+ data.tar.gz: 1ecd149e3f5efed0fb906b75499789610b5305dbb816b95dbad72074a022937a1f42126556faedd248d9a96e71a5c8e40d87fbb06e4cb2f4f1736cf443b9a691
data/README.md ADDED
@@ -0,0 +1,343 @@
1
+ # konfidant-ruby
2
+
3
+ [![Test](https://github.com/konfidant/sdk-ruby/actions/workflows/test.yml/badge.svg)](https://github.com/konfidant/sdk-ruby/actions/workflows/test.yml)
4
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/eb3798f7eb59412abc2bd3ce307760b5)](https://app.codacy.com/gh/konfidant/sdk-ruby/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
5
+ [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/eb3798f7eb59412abc2bd3ce307760b5)](https://app.codacy.com/gh/konfidant/sdk-ruby/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
6
+
7
+ Official Ruby SDK for the [Konfidant](https://www.konfidant.app) API.
8
+
9
+ Konfidant lets you share secrets — encrypted text and files — that self-destruct after being read.
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ Add to your `Gemfile`:
16
+
17
+ ```ruby
18
+ gem 'konfidant'
19
+ ```
20
+
21
+ Then run:
22
+
23
+ ```bash
24
+ bundle install
25
+ ```
26
+
27
+ Or install directly:
28
+
29
+ ```bash
30
+ gem install konfidant
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Quick start
36
+
37
+ ```ruby
38
+ require 'konfidant'
39
+
40
+ client = Konfidant::Client.new(api_key: 'your-api-key')
41
+
42
+ result = client.share_text(text: 'super-secret-password', ttl_hours: 24)
43
+
44
+ puts "Share this link: #{result.share_url}"
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Authentication
50
+
51
+ All requests require a Bearer API key. Generate one from the [Konfidant dashboard](https://www.konfidant.app).
52
+
53
+ ```ruby
54
+ client = Konfidant::Client.new(api_key: ENV['KONFIDANT_API_KEY'])
55
+ ```
56
+
57
+ ---
58
+
59
+ ## API Reference
60
+
61
+ ### `Konfidant::Client.new`
62
+
63
+ | Option | Type | Required | Description |
64
+ |----------------|-----------|----------|----------------------------------------------------------------------|
65
+ | `api_key` | `String` | Yes | Your Konfidant API key |
66
+ | `base_url` | `String` | No | Override the base URL (default: `https://www.konfidant.app`) |
67
+ | `http_timeout` | `Integer` | No | Per-request HTTP timeout in seconds (default: `120`; `nil` disables) |
68
+
69
+ Raises `ArgumentError` if `api_key` is nil or empty.
70
+
71
+ ---
72
+
73
+ ### `client.share_text(text:, ttl_hours:)`
74
+
75
+ Encrypt and share a text message.
76
+
77
+ | Argument | Type | Description |
78
+ |-------------|-----------|--------------------------|
79
+ | `text` | `String` | The secret text to share |
80
+ | `ttl_hours` | `Integer` | Time-to-live in hours |
81
+
82
+ Returns a `Konfidant::ShareTextResponse`:
83
+
84
+ | Field | Type | Description |
85
+ |----------------|-----------|---------------------------------------------|
86
+ | `text_id` | `String` | Unique ID of the shared text |
87
+ | `share_url` | `String` | One-time download link to send to recipient |
88
+ | `expires_at` | `String` | Expiry datetime |
89
+ | `verified_burn`| `Boolean` | Whether burn-on-read is verified |
90
+
91
+ #### Example: share text
92
+
93
+ ```ruby
94
+ result = client.share_text(text: 'db-password: hunter2', ttl_hours: 48)
95
+
96
+ puts result.share_url # send to recipient
97
+ puts result.expires_at
98
+ puts result.verified_burn
99
+ ```
100
+
101
+ ---
102
+
103
+ ### `client.share_file(filename:, file_size:, ttl_hours:)`
104
+
105
+ Request a presigned upload URL for a file. Use the returned response with `upload_file` to complete
106
+ the upload, then poll `get_file_status` for the share link.
107
+
108
+ > For a one-call convenience wrapper, see [`share_and_upload_file`](#clientshare_and_upload_file).
109
+
110
+ | Argument | Type | Description |
111
+ |-------------|-----------|----------------------------------|
112
+ | `filename` | `String` | Original filename with extension |
113
+ | `file_size` | `Integer` | File size in bytes |
114
+ | `ttl_hours` | `Integer` | Time-to-live in hours |
115
+
116
+ Returns a `Konfidant::ShareFileResponse`:
117
+
118
+ | Field | Type | Description |
119
+ |--------------------|-------------------------------|-------------------------------------------------|
120
+ | `upload_url` | `String` | Short-lived presigned S3 PUT URL |
121
+ | `file_key` | `String` | Use with `get_file_status` and `upload_file` |
122
+ | `poll_url` | `String` | Convenience URL for status polling |
123
+ | `metadata_headers` | `Konfidant::FileMetadataHeaders` | Required S3 headers — passed by `upload_file` |
124
+
125
+ ---
126
+
127
+ ### `client.upload_file(io:, size:, content_type:, presigned:)`
128
+
129
+ Upload file bytes to the presigned S3 URL from `share_file`. Automatically attaches the required
130
+ S3 metadata headers. Does **not** send the Konfidant `Authorization` header to S3.
131
+
132
+ | Argument | Type | Description |
133
+ |----------------|-----------------------------|---------------------------------------|
134
+ | `io` | `IO` | Readable IO object (File, StringIO) |
135
+ | `size` | `Integer` | Content-Length in bytes |
136
+ | `content_type` | `String` | MIME type (e.g. `application/pdf`) |
137
+ | `presigned` | `Konfidant::ShareFileResponse` | Full response from `share_file` |
138
+
139
+ Returns `nil` on success. Raises `Konfidant::ApiError` on S3 error.
140
+
141
+ #### Example: manual three-step flow
142
+
143
+ ```ruby
144
+ file = File.open('report.pdf', 'rb')
145
+ size = File.size('report.pdf')
146
+
147
+ # Step 1 – get presigned URL
148
+ presigned = client.share_file(
149
+ filename: 'report.pdf',
150
+ file_size: size,
151
+ ttl_hours: 72
152
+ )
153
+
154
+ # Step 2 – upload to S3
155
+ client.upload_file(
156
+ io: file,
157
+ size: size,
158
+ content_type: 'application/pdf',
159
+ presigned: presigned
160
+ )
161
+
162
+ # Step 3 – poll for share link
163
+ loop do
164
+ status = client.get_file_status(presigned.file_key)
165
+ if status.status == 'complete'
166
+ puts "Share URL: #{status.share_url}"
167
+ break
168
+ end
169
+ sleep 2
170
+ end
171
+ ```
172
+
173
+ ---
174
+
175
+ ### `client.get_file_status(file_key)`
176
+
177
+ Poll the encryption status of an uploaded file.
178
+
179
+ | Argument | Type | Description |
180
+ |------------|----------|-----------------------------------------------|
181
+ | `file_key` | `String` | The `file_key` from the `share_file` response |
182
+
183
+ Returns a `Konfidant::FileStatusResponse`:
184
+
185
+ | Field | Type | When set |
186
+ |----------------|-----------|-----------------------------------------|
187
+ | `status` | `String` | Always (`"processing"` or `"complete"`) |
188
+ | `message` | `String` | When `processing` |
189
+ | `file_id` | `String` | When `complete` |
190
+ | `file_name` | `String` | When `complete` |
191
+ | `share_url` | `String` | When `complete` |
192
+ | `expires_at` | `String` | When `complete` |
193
+ | `verified_burn`| `Boolean` | When `complete` |
194
+
195
+ ---
196
+
197
+ ### `client.list_shares(type: nil, status: nil, limit: nil, offset: nil)`
198
+
199
+ List all shares for the authenticated organization. All parameters are optional.
200
+
201
+ | Argument | Type | Description |
202
+ |----------|----------|--------------------------------|
203
+ | `type` | `String` | `"file"` or `"text"` |
204
+ | `status` | `String` | `"active"` or `"accessed"` |
205
+ | `limit` | `Integer`| Page size (default 50) |
206
+ | `offset` | `Integer`| Pagination offset |
207
+
208
+ Returns a `Konfidant::ListSharesResponse`:
209
+
210
+ | Field | Type | Description |
211
+ |--------------|----------------------------|---------------------|
212
+ | `shares` | `Array<Konfidant::Share>` | Share entries |
213
+ | `pagination` | `Konfidant::Pagination` | Page metadata |
214
+
215
+ Each `Konfidant::Share`:
216
+
217
+ | Field | Type | Description |
218
+ |------------------|---------------|-------------------------|
219
+ | `type` | `String` | `"file"` or `"text"` |
220
+ | `file_name` | `String` | Filename or text label |
221
+ | `file_size_bytes`| `Integer` | Size in bytes |
222
+ | `created_at` | `String` | Creation datetime |
223
+ | `expires_at` | `String` | Expiry datetime |
224
+ | `accessed_at` | `String, nil` | Access datetime, or nil |
225
+ | `created_by` | `String` | Email of creator |
226
+
227
+ `Konfidant::Pagination`:
228
+
229
+ | Field | Type | Description |
230
+ |------------|-----------|--------------------------|
231
+ | `total` | `Integer` | Total number of shares |
232
+ | `limit` | `Integer` | Page size used |
233
+ | `offset` | `Integer` | Offset used |
234
+ | `has_more` | `Boolean` | Whether more pages exist |
235
+
236
+ #### Example: list shares
237
+
238
+ ```ruby
239
+ result = client.list_shares(type: 'file', limit: 10)
240
+
241
+ result.shares.each do |share|
242
+ puts "#{share.file_name} — expires #{share.expires_at}"
243
+ end
244
+
245
+ puts "Total: #{result.pagination.total}"
246
+ puts "More? #{result.pagination.has_more}"
247
+ ```
248
+
249
+ ---
250
+
251
+ ### `client.share_and_upload_file`
252
+
253
+ Convenience wrapper that calls `share_file` → `upload_file` → polls `get_file_status` until complete.
254
+
255
+ ```ruby
256
+ client.share_and_upload_file(
257
+ io: io,
258
+ size: size,
259
+ filename: filename,
260
+ content_type: content_type,
261
+ ttl_hours: ttl_hours,
262
+ poll_interval: 2, # seconds between status checks (default: 2)
263
+ timeout: 60 # max wait for encryption in seconds (default: 60)
264
+ )
265
+ ```
266
+
267
+ | Argument | Type | Default | Description |
268
+ |-----------------|-----------|---------|-------------------------------------|
269
+ | `io` | `IO` | — | Readable IO object (File, StringIO) |
270
+ | `size` | `Integer` | — | File size in bytes |
271
+ | `filename` | `String` | — | Filename with extension |
272
+ | `content_type` | `String` | — | MIME type |
273
+ | `ttl_hours` | `Integer` | — | Time-to-live in hours |
274
+ | `poll_interval` | `Numeric` | `2` | Seconds between status checks |
275
+ | `timeout` | `Numeric` | `60` | Max seconds to wait for encryption |
276
+
277
+ Returns a `Konfidant::ShareResult`:
278
+
279
+ | Field | Type | Description |
280
+ |----------------|-----------|--------------------------------|
281
+ | `share_url` | `String` | One-time download link |
282
+ | `file_id` | `String` | Unique file ID |
283
+ | `expires_at` | `String` | Expiry datetime |
284
+ | `verified_burn`| `Boolean` | Whether burn-on-read is active |
285
+
286
+ Raises `RuntimeError` with `"konfidant: encryption timed out after Ns"` if encryption does not complete
287
+ within `timeout` seconds.
288
+
289
+ #### Example: share and upload file
290
+
291
+ ```ruby
292
+ file = File.open('confidential.zip', 'rb')
293
+ size = File.size('confidential.zip')
294
+
295
+ result = client.share_and_upload_file(
296
+ io: file,
297
+ size: size,
298
+ filename: 'confidential.zip',
299
+ content_type: 'application/zip',
300
+ ttl_hours: 48
301
+ )
302
+
303
+ puts "Ready to share: #{result.share_url}"
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Error handling
309
+
310
+ All API and S3 errors raise `Konfidant::ApiError`.
311
+
312
+ ```ruby
313
+ begin
314
+ client.share_text(text: 'secret', ttl_hours: 1)
315
+ rescue Konfidant::ApiError => e
316
+ puts e.message # e.g. "Missing or invalid Authorization header."
317
+ puts e.status_code # e.g. 401
318
+ puts e.body.inspect # parsed response body (Hash or String)
319
+ end
320
+ ```
321
+
322
+ ### Common error codes
323
+
324
+ | Status | Meaning |
325
+ |--------|----------------------------|
326
+ | `400` | Bad request / invalid body |
327
+ | `401` | Missing or invalid API key |
328
+ | `403` | Insufficient API key scope |
329
+ | `404` | Resource not found |
330
+
331
+ ---
332
+
333
+ ## Development
334
+
335
+ ```bash
336
+ bundle install # install dependencies
337
+ bundle exec rspec # run tests
338
+ ```
339
+
340
+ ### Requirements
341
+
342
+ - Ruby >= 3.2.0
343
+ - No runtime dependencies (uses stdlib `net/http`, `uri`, `json`)
@@ -0,0 +1,185 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module Konfidant
6
+ DEFAULT_BASE_URL = 'https://www.konfidant.app'
7
+ DEFAULT_TIMEOUT = 120
8
+
9
+ class Client
10
+ def initialize(api_key:, base_url: nil, http_timeout: DEFAULT_TIMEOUT)
11
+ raise ArgumentError, 'api_key is required' if api_key.nil? || api_key.empty?
12
+
13
+ @api_key = api_key
14
+ @base_url = (base_url || DEFAULT_BASE_URL).sub(%r{/+\z}, '')
15
+ @http_timeout = http_timeout
16
+ end
17
+
18
+ def share_text(text:, ttl_hours:)
19
+ body = request(:post, '/api/v1/texts', { text: text, ttl_hours: ttl_hours })
20
+ ShareTextResponse.new(
21
+ text_id: body['text_id'],
22
+ share_url: body['share_url'],
23
+ expires_at: body['expires_at'],
24
+ verified_burn: body['verified_burn']
25
+ )
26
+ end
27
+
28
+ def share_file(filename:, file_size:, ttl_hours:)
29
+ body = request(:post, '/api/v1/files', { filename: filename, file_size: file_size, ttl_hours: ttl_hours })
30
+ h = body['metadata_headers']
31
+ ShareFileResponse.new(
32
+ upload_url: body['upload_url'],
33
+ file_key: body['file_key'],
34
+ metadata_headers: FileMetadataHeaders.new(
35
+ user_id: h['x-amz-meta-user-id'],
36
+ ttl_hours: h['x-amz-meta-ttl-hours'],
37
+ organization_id: h['x-amz-meta-organization-id']
38
+ ),
39
+ poll_url: body['poll_url']
40
+ )
41
+ end
42
+
43
+ def get_file_status(file_key)
44
+ encoded = encode_path_segment(file_key)
45
+ body = request(:get, "/api/v1/files/#{encoded}/status")
46
+ FileStatusResponse.new(
47
+ status: body['status'],
48
+ message: body['message'],
49
+ file_id: body['file_id'],
50
+ file_name: body['file_name'],
51
+ share_url: body['share_url'],
52
+ expires_at: body['expires_at'],
53
+ verified_burn: body['verified_burn']
54
+ )
55
+ end
56
+
57
+ def list_shares(type: nil, status: nil, limit: nil, offset: nil)
58
+ params = {}
59
+ params[:type] = type if type
60
+ params[:status] = status if status
61
+ params[:limit] = limit if limit
62
+ params[:offset] = offset if offset
63
+
64
+ path = '/api/v1/shares'
65
+ path += "?#{URI.encode_www_form(params)}" unless params.empty?
66
+
67
+ body = request(:get, path)
68
+ ListSharesResponse.new(
69
+ shares: body['shares'].map { |s| parse_share(s) },
70
+ pagination: parse_pagination(body['pagination'])
71
+ )
72
+ end
73
+
74
+ def upload_file(io:, size:, content_type:, presigned:)
75
+ uri = URI.parse(presigned.upload_url)
76
+ http = build_http(uri)
77
+
78
+ req = Net::HTTP::Put.new(uri.request_uri)
79
+ req['Content-Type'] = content_type
80
+ req['Content-Length'] = size.to_s
81
+ req['x-amz-meta-organization-id'] = presigned.metadata_headers.organization_id
82
+ req['x-amz-meta-ttl-hours'] = presigned.metadata_headers.ttl_hours
83
+ req['x-amz-meta-user-id'] = presigned.metadata_headers.user_id
84
+ req.body_stream = io
85
+
86
+ resp = http.request(req)
87
+ return if resp.code.to_i.between?(200, 299)
88
+
89
+ raise ApiError.new("file upload failed: HTTP #{resp.code}", resp.code.to_i, resp.body)
90
+ end
91
+
92
+ def share_and_upload_file(io:, size:, filename:, content_type:, ttl_hours:,
93
+ poll_interval: 2, timeout: 60)
94
+ presigned = share_file(filename: filename, file_size: size, ttl_hours: ttl_hours)
95
+ upload_file(io: io, size: size, content_type: content_type, presigned: presigned)
96
+
97
+ deadline = Time.now + timeout
98
+ while Time.now < deadline
99
+ status = get_file_status(presigned.file_key)
100
+ if status.status == 'complete'
101
+ return ShareResult.new(
102
+ share_url: status.share_url,
103
+ file_id: status.file_id,
104
+ expires_at: status.expires_at,
105
+ verified_burn: status.verified_burn
106
+ )
107
+ end
108
+ sleep(poll_interval)
109
+ end
110
+
111
+ raise "konfidant: encryption timed out after #{timeout}s"
112
+ end
113
+
114
+ private
115
+
116
+ def auth_headers
117
+ {
118
+ 'Authorization' => "Bearer #{@api_key}",
119
+ 'Content-Type' => 'application/json'
120
+ }
121
+ end
122
+
123
+ def build_http(uri)
124
+ http = Net::HTTP.new(uri.host, uri.port)
125
+ http.use_ssl = uri.scheme == 'https'
126
+ unless @http_timeout.nil?
127
+ http.open_timeout = @http_timeout
128
+ http.read_timeout = @http_timeout
129
+ end
130
+ http
131
+ end
132
+
133
+ def request(method, path, payload = nil)
134
+ uri = URI.parse("#{@base_url}#{path}")
135
+ http = build_http(uri)
136
+
137
+ req = case method
138
+ when :get then Net::HTTP::Get.new(uri.request_uri)
139
+ when :post then Net::HTTP::Post.new(uri.request_uri)
140
+ end
141
+
142
+ auth_headers.each { |k, v| req[k] = v }
143
+ req.body = JSON.generate(payload) if payload
144
+
145
+ resp = http.request(req)
146
+ content_type = resp['content-type'] || ''
147
+
148
+ body = if content_type.include?('application/json')
149
+ JSON.parse(resp.body)
150
+ else
151
+ resp.body
152
+ end
153
+
154
+ return body if resp.code.to_i.between?(200, 299)
155
+
156
+ message = body.is_a?(Hash) && body['error'] ? body['error'] : "HTTP #{resp.code}"
157
+ raise ApiError.new(message, resp.code.to_i, body)
158
+ end
159
+
160
+ def encode_path_segment(str)
161
+ str.gsub(/[^A-Za-z0-9\-._~]/) { |c| c.bytes.map { |b| format('%%%02X', b) }.join }
162
+ end
163
+
164
+ def parse_share(s)
165
+ Share.new(
166
+ type: s['type'],
167
+ file_name: s['file_name'],
168
+ file_size_bytes: s['file_size_bytes'],
169
+ created_at: s['created_at'],
170
+ expires_at: s['expires_at'],
171
+ accessed_at: s['accessed_at'],
172
+ created_by: s['created_by']
173
+ )
174
+ end
175
+
176
+ def parse_pagination(p)
177
+ Pagination.new(
178
+ total: p['total'],
179
+ limit: p['limit'],
180
+ offset: p['offset'],
181
+ has_more: p['has_more']
182
+ )
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,11 @@
1
+ module Konfidant
2
+ class ApiError < StandardError
3
+ attr_reader :status_code, :body
4
+
5
+ def initialize(message, status_code, body)
6
+ super(message)
7
+ @status_code = status_code
8
+ @body = body
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Konfidant
2
+ FileMetadataHeaders = Data.define(:user_id, :ttl_hours, :organization_id)
3
+
4
+ ShareTextResponse = Data.define(:text_id, :share_url, :expires_at, :verified_burn)
5
+
6
+ ShareFileResponse = Data.define(:upload_url, :file_key, :metadata_headers, :poll_url)
7
+
8
+ FileStatusResponse = Data.define(
9
+ :status, :message, :file_id, :file_name, :share_url, :expires_at, :verified_burn
10
+ ) do
11
+ def initialize(status:, message: nil, file_id: nil, file_name: nil,
12
+ share_url: nil, expires_at: nil, verified_burn: nil)
13
+ super
14
+ end
15
+ end
16
+
17
+ Share = Data.define(
18
+ :type, :file_name, :file_size_bytes, :created_at, :expires_at, :accessed_at, :created_by
19
+ ) do
20
+ def initialize(type:, file_name:, file_size_bytes:, created_at:,
21
+ expires_at:, created_by:, accessed_at: nil)
22
+ super
23
+ end
24
+ end
25
+
26
+ Pagination = Data.define(:total, :limit, :offset, :has_more)
27
+
28
+ ListSharesResponse = Data.define(:shares, :pagination)
29
+
30
+ ShareResult = Data.define(:share_url, :file_id, :expires_at, :verified_burn)
31
+ end
@@ -0,0 +1,3 @@
1
+ module Konfidant
2
+ VERSION = '0.7.0'
3
+ end
data/lib/konfidant.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative 'konfidant/version'
2
+ require_relative 'konfidant/errors'
3
+ require_relative 'konfidant/types'
4
+ require_relative 'konfidant/client'
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: konfidant
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ platform: ruby
6
+ authors:
7
+ - Konfidant
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.13'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.13'
40
+ - !ruby/object:Gem::Dependency
41
+ name: webmock
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.23'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.23'
54
+ - !ruby/object:Gem::Dependency
55
+ name: simplecov
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.22'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.22'
68
+ - !ruby/object:Gem::Dependency
69
+ name: simplecov-lcov
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.8'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.8'
82
+ email:
83
+ - hello@konfidant.app
84
+ executables: []
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - README.md
89
+ - lib/konfidant.rb
90
+ - lib/konfidant/client.rb
91
+ - lib/konfidant/errors.rb
92
+ - lib/konfidant/types.rb
93
+ - lib/konfidant/version.rb
94
+ homepage: https://github.com/konfidant/sdk-ruby
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 3.2.0
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubygems_version: 4.0.10
113
+ specification_version: 4
114
+ summary: Official Ruby SDK for the Konfidant API
115
+ test_files: []