leash-sdk 0.1.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: 8f4dcd4f62f60a894e5ec9b312dc228d3681fde09dab44d5d1c0af60eb3bf314
4
+ data.tar.gz: ff13bfe8d387c43d15852f9797d058c902afdeb0ead32444fbd753363b3f10b3
5
+ SHA512:
6
+ metadata.gz: 70e3236a21000973213bd657a8ee8e3d0889e5d1e5479dc1d6ab70eed448b1f0c31e0548a146c6361acd3eebfc26bd1b98b31ffd08f593b6ff4a79a6892945ab
7
+ data.tar.gz: c9695b235f9b62181cde8794583eb9f59ea5d2b049faa8a52bcd601e7a297aeffe713b4cb02b48034d6bedadb48694eb2945a88355835a16c57c49c3b2c45391
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leash
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,140 @@
1
+ # Leash SDK for Ruby
2
+
3
+ Ruby SDK for the [Leash](https://leash.build) platform integrations API. Access Gmail, Google Calendar, Google Drive, and more through the Leash platform proxy.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "leash-sdk"
11
+ ```
12
+
13
+ Or install directly:
14
+
15
+ ```
16
+ gem install leash-sdk
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```ruby
22
+ require "leash"
23
+
24
+ client = Leash::Integrations.new(auth_token: ENV["LEASH_AUTH_TOKEN"])
25
+
26
+ # Gmail
27
+ messages = client.gmail.list_messages(query: "is:unread", max_results: 10)
28
+ message = client.gmail.get_message("msg_id_123")
29
+ client.gmail.send_message(to: "friend@example.com", subject: "Hello", body: "Hi there!")
30
+ labels = client.gmail.list_labels
31
+
32
+ # Google Calendar
33
+ calendars = client.calendar.list_calendars
34
+ events = client.calendar.list_events(
35
+ time_min: "2026-04-10T00:00:00Z",
36
+ time_max: "2026-04-17T00:00:00Z",
37
+ single_events: true,
38
+ order_by: "startTime"
39
+ )
40
+ client.calendar.create_event(
41
+ summary: "Team standup",
42
+ start: { "dateTime" => "2026-04-11T09:00:00-04:00" },
43
+ end_time: { "dateTime" => "2026-04-11T09:30:00-04:00" }
44
+ )
45
+
46
+ # Google Drive
47
+ files = client.drive.list_files
48
+ file = client.drive.get_file("file_id_123")
49
+ results = client.drive.search_files("quarterly report", max_results: 5)
50
+ ```
51
+
52
+ ## Connection Management
53
+
54
+ ```ruby
55
+ # Check if a provider is connected
56
+ client.connected?("gmail") # => true/false
57
+
58
+ # Get all connections
59
+ client.connections # => [{ "providerId" => "gmail", "status" => "active", ... }]
60
+
61
+ # Get OAuth connect URL (for UI buttons)
62
+ url = client.connect_url("gmail", return_url: "https://myapp.com/settings")
63
+ ```
64
+
65
+ ## Error Handling
66
+
67
+ ```ruby
68
+ begin
69
+ client.gmail.list_messages
70
+ rescue Leash::NotConnectedError => e
71
+ # Redirect user to connect: e.connect_url
72
+ puts "Please connect Gmail: #{e.connect_url}"
73
+ rescue Leash::TokenExpiredError => e
74
+ # Token needs refresh: e.connect_url
75
+ puts "Token expired, reconnect: #{e.connect_url}"
76
+ rescue Leash::Error => e
77
+ # General API error
78
+ puts "Error (#{e.code}): #{e.message}"
79
+ end
80
+ ```
81
+
82
+ ## Configuration
83
+
84
+ ```ruby
85
+ # Custom platform URL
86
+ client = Leash::Integrations.new(
87
+ auth_token: "your-token",
88
+ platform_url: "https://your-instance.leash.build"
89
+ )
90
+ ```
91
+
92
+ ## API Reference
93
+
94
+ ### `Leash::Integrations.new(auth_token:, platform_url: "https://leash.build")`
95
+
96
+ Creates a new client instance.
97
+
98
+ ### Gmail (`client.gmail`)
99
+
100
+ | Method | Description |
101
+ |--------|-------------|
102
+ | `list_messages(query:, max_results:, label_ids:, page_token:)` | List messages |
103
+ | `get_message(message_id, format:)` | Get a message by ID |
104
+ | `send_message(to:, subject:, body:, cc:, bcc:)` | Send an email |
105
+ | `search_messages(query, max_results:)` | Search messages |
106
+ | `list_labels` | List all labels |
107
+
108
+ ### Calendar (`client.calendar`)
109
+
110
+ | Method | Description |
111
+ |--------|-------------|
112
+ | `list_calendars` | List all calendars |
113
+ | `list_events(calendar_id:, time_min:, time_max:, max_results:, single_events:, order_by:)` | List events |
114
+ | `create_event(summary:, start:, end_time:, calendar_id:, description:, location:, attendees:)` | Create an event |
115
+ | `get_event(event_id, calendar_id:)` | Get an event by ID |
116
+
117
+ ### Drive (`client.drive`)
118
+
119
+ | Method | Description |
120
+ |--------|-------------|
121
+ | `list_files(query:, max_results:, folder_id:)` | List files |
122
+ | `get_file(file_id)` | Get file metadata |
123
+ | `search_files(query, max_results:)` | Search files |
124
+
125
+ ### Connections
126
+
127
+ | Method | Description |
128
+ |--------|-------------|
129
+ | `connected?(provider_id)` | Check if provider is connected |
130
+ | `connections` | Get all connection statuses |
131
+ | `connect_url(provider_id, return_url:)` | Get OAuth connect URL |
132
+
133
+ ## Requirements
134
+
135
+ - Ruby >= 3.0
136
+ - No external dependencies (uses stdlib `net/http`, `json`, `uri`)
137
+
138
+ ## License
139
+
140
+ MIT
data/leash-sdk.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/leash"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "leash-sdk"
7
+ spec.version = Leash::VERSION
8
+ spec.authors = ["Leash"]
9
+ spec.email = ["hello@leash.build"]
10
+
11
+ spec.summary = "Ruby SDK for the Leash platform integrations API"
12
+ spec.description = "Access Gmail, Google Calendar, Google Drive, and more through the Leash platform proxy. No API keys needed -- uses your Leash auth token."
13
+ spec.homepage = "https://github.com/leash-build/leash-sdk-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ spec.files = Dir["lib/**/*.rb"] + ["leash-sdk.gemspec", "Gemfile", "README.md", "LICENSE"]
22
+ spec.require_paths = ["lib"]
23
+
24
+ # No runtime dependencies -- stdlib only (net/http, json, uri).
25
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leash
4
+ # Client for Google Calendar operations via the Leash platform proxy.
5
+ #
6
+ # Not instantiated directly -- use {Integrations#calendar} instead.
7
+ class CalendarClient
8
+ PROVIDER = "google_calendar"
9
+
10
+ # @api private
11
+ def initialize(call_fn)
12
+ @call = call_fn
13
+ end
14
+
15
+ # List all calendars accessible to the user.
16
+ #
17
+ # @return [Hash] hash with calendar list data
18
+ def list_calendars
19
+ @call.call(PROVIDER, "list-calendars", {})
20
+ end
21
+
22
+ # List events on a calendar.
23
+ #
24
+ # @param calendar_id [String, nil] calendar identifier (defaults to "primary" on the server)
25
+ # @param time_min [String, nil] lower bound for event start time (RFC 3339)
26
+ # @param time_max [String, nil] upper bound for event start time (RFC 3339)
27
+ # @param max_results [Integer, nil] maximum number of events to return
28
+ # @param single_events [Boolean, nil] whether to expand recurring events
29
+ # @param order_by [String, nil] sort order (e.g. "startTime", "updated")
30
+ # @return [Hash] hash with "items" list of events
31
+ def list_events(calendar_id: nil, time_min: nil, time_max: nil, max_results: nil, single_events: nil, order_by: nil)
32
+ params = {}
33
+ params["calendarId"] = calendar_id if calendar_id
34
+ params["timeMin"] = time_min if time_min
35
+ params["timeMax"] = time_max if time_max
36
+ params["maxResults"] = max_results if max_results
37
+ params["singleEvents"] = single_events unless single_events.nil?
38
+ params["orderBy"] = order_by if order_by
39
+ @call.call(PROVIDER, "list-events", params)
40
+ end
41
+
42
+ # Create a new calendar event.
43
+ #
44
+ # @param summary [String] event title
45
+ # @param start [Hash, String] start time -- either an RFC 3339 string or a hash
46
+ # with keys "dateTime", "date", and/or "timeZone"
47
+ # @param end_time [Hash, String] end time (same format as +start+)
48
+ # @param calendar_id [String, nil] calendar identifier (defaults to "primary")
49
+ # @param description [String, nil] event description
50
+ # @param location [String, nil] event location
51
+ # @param attendees [Array<Hash>, nil] list of attendee hashes (e.g. [{ "email" => "a@b.com" }])
52
+ # @return [Hash] the created event object
53
+ def create_event(summary:, start:, end_time:, calendar_id: nil, description: nil, location: nil, attendees: nil)
54
+ params = { "summary" => summary, "start" => start, "end" => end_time }
55
+ params["calendarId"] = calendar_id if calendar_id
56
+ params["description"] = description if description
57
+ params["location"] = location if location
58
+ params["attendees"] = attendees if attendees
59
+ @call.call(PROVIDER, "create-event", params)
60
+ end
61
+
62
+ # Get a single event by ID.
63
+ #
64
+ # @param event_id [String] the event identifier
65
+ # @param calendar_id [String, nil] the calendar identifier
66
+ # @return [Hash] the event object
67
+ def get_event(event_id, calendar_id: nil)
68
+ params = { "eventId" => event_id }
69
+ params["calendarId"] = calendar_id if calendar_id
70
+ @call.call(PROVIDER, "get-event", params)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leash
4
+ # Client for Google Drive operations via the Leash platform proxy.
5
+ #
6
+ # Not instantiated directly -- use {Integrations#drive} instead.
7
+ class DriveClient
8
+ PROVIDER = "google_drive"
9
+
10
+ # @api private
11
+ def initialize(call_fn)
12
+ @call = call_fn
13
+ end
14
+
15
+ # List files in the user's Drive.
16
+ #
17
+ # @param query [String, nil] Drive search query (Google Drive API query syntax)
18
+ # @param max_results [Integer, nil] maximum number of files to return
19
+ # @param folder_id [String, nil] restrict to files within a specific folder
20
+ # @return [Hash] hash with "files" list
21
+ def list_files(query: nil, max_results: nil, folder_id: nil)
22
+ params = {}
23
+ params["query"] = query if query
24
+ params["maxResults"] = max_results if max_results
25
+ params["folderId"] = folder_id if folder_id
26
+ @call.call(PROVIDER, "list-files", params)
27
+ end
28
+
29
+ # Get file metadata by ID.
30
+ #
31
+ # @param file_id [String] the file identifier
32
+ # @return [Hash] the file metadata object
33
+ def get_file(file_id)
34
+ @call.call(PROVIDER, "get-file", { "fileId" => file_id })
35
+ end
36
+
37
+ # Search files using a query string.
38
+ #
39
+ # @param query [String] search query (Google Drive API query syntax)
40
+ # @param max_results [Integer, nil] maximum number of results
41
+ # @return [Hash] hash with "files" list
42
+ def search_files(query, max_results: nil)
43
+ params = { "query" => query }
44
+ params["maxResults"] = max_results if max_results
45
+ @call.call(PROVIDER, "search-files", params)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leash
4
+ # Base error class for all Leash SDK errors.
5
+ #
6
+ # @attr_reader [String, nil] code the error code from the platform
7
+ # @attr_reader [String, nil] connect_url the OAuth connect URL (present when provider is not connected)
8
+ class Error < StandardError
9
+ attr_reader :code, :connect_url
10
+
11
+ # @param message [String] human-readable error message
12
+ # @param code [String, nil] machine-readable error code
13
+ # @param connect_url [String, nil] URL to initiate OAuth connection
14
+ def initialize(message, code: nil, connect_url: nil)
15
+ super(message)
16
+ @code = code
17
+ @connect_url = connect_url
18
+ end
19
+ end
20
+
21
+ # Raised when the provider is not connected for the current user.
22
+ class NotConnectedError < Error
23
+ def initialize(message = "Integration not connected", connect_url: nil)
24
+ super(message, code: "not_connected", connect_url: connect_url)
25
+ end
26
+ end
27
+
28
+ # Raised when the OAuth token has expired and needs to be refreshed.
29
+ class TokenExpiredError < Error
30
+ def initialize(message = "Token expired", connect_url: nil)
31
+ super(message, code: "token_expired", connect_url: connect_url)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leash
4
+ # Client for Gmail operations via the Leash platform proxy.
5
+ #
6
+ # Not instantiated directly -- use {Integrations#gmail} instead.
7
+ class GmailClient
8
+ PROVIDER = "gmail"
9
+
10
+ # @api private
11
+ def initialize(call_fn)
12
+ @call = call_fn
13
+ end
14
+
15
+ # List messages in the user's mailbox.
16
+ #
17
+ # @param query [String, nil] Gmail search query (e.g. "from:user@example.com")
18
+ # @param max_results [Integer] maximum number of messages to return
19
+ # @param label_ids [Array<String>, nil] filter by label IDs (e.g. ["INBOX"])
20
+ # @param page_token [String, nil] token for fetching the next page of results
21
+ # @return [Hash] hash with "messages", "nextPageToken", and "resultSizeEstimate"
22
+ def list_messages(query: nil, max_results: 20, label_ids: nil, page_token: nil)
23
+ params = { "maxResults" => max_results }
24
+ params["query"] = query if query
25
+ params["labelIds"] = label_ids if label_ids
26
+ params["pageToken"] = page_token if page_token
27
+ @call.call(PROVIDER, "list-messages", params)
28
+ end
29
+
30
+ # Get a single message by ID.
31
+ #
32
+ # @param message_id [String] the message ID
33
+ # @param format [String] response format ("full", "metadata", "minimal", "raw")
34
+ # @return [Hash] the full message object
35
+ def get_message(message_id, format: "full")
36
+ @call.call(PROVIDER, "get-message", { "messageId" => message_id, "format" => format })
37
+ end
38
+
39
+ # Send an email message.
40
+ #
41
+ # @param to [String] recipient email address
42
+ # @param subject [String] email subject line
43
+ # @param body [String] email body text
44
+ # @param cc [String, nil] CC recipient(s)
45
+ # @param bcc [String, nil] BCC recipient(s)
46
+ # @return [Hash] the sent message metadata
47
+ def send_message(to:, subject:, body:, cc: nil, bcc: nil)
48
+ params = { "to" => to, "subject" => subject, "body" => body }
49
+ params["cc"] = cc if cc
50
+ params["bcc"] = bcc if bcc
51
+ @call.call(PROVIDER, "send-message", params)
52
+ end
53
+
54
+ # Search messages using a Gmail query string.
55
+ #
56
+ # @param query [String] Gmail search query
57
+ # @param max_results [Integer] maximum number of results to return
58
+ # @return [Hash] hash with "messages", "nextPageToken", and "resultSizeEstimate"
59
+ def search_messages(query, max_results: 20)
60
+ @call.call(PROVIDER, "search-messages", { "query" => query, "maxResults" => max_results })
61
+ end
62
+
63
+ # List all labels in the user's mailbox.
64
+ #
65
+ # @return [Hash] hash with "labels" list
66
+ def list_labels
67
+ @call.call(PROVIDER, "list-labels", {})
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+
7
+ require_relative "errors"
8
+ require_relative "gmail"
9
+ require_relative "calendar"
10
+ require_relative "drive"
11
+
12
+ module Leash
13
+ DEFAULT_PLATFORM_URL = "https://leash.build"
14
+
15
+ # Main client for accessing Leash platform integrations.
16
+ #
17
+ # @example
18
+ # client = Leash::Integrations.new(auth_token: "your-jwt-token")
19
+ # messages = client.gmail.list_messages(query: "is:unread")
20
+ # events = client.calendar.list_events(time_min: "2026-04-10T00:00:00Z")
21
+ # files = client.drive.list_files
22
+ class Integrations
23
+ # @param auth_token [String] the leash-auth JWT token
24
+ # @param platform_url [String] base URL of the Leash platform API
25
+ def initialize(auth_token:, platform_url: DEFAULT_PLATFORM_URL)
26
+ @auth_token = auth_token
27
+ @platform_url = platform_url.chomp("/")
28
+ end
29
+
30
+ # Gmail integration client.
31
+ #
32
+ # @return [GmailClient]
33
+ def gmail
34
+ @gmail ||= GmailClient.new(method(:call))
35
+ end
36
+
37
+ # Google Calendar integration client.
38
+ #
39
+ # @return [CalendarClient]
40
+ def calendar
41
+ @calendar ||= CalendarClient.new(method(:call))
42
+ end
43
+
44
+ # Google Drive integration client.
45
+ #
46
+ # @return [DriveClient]
47
+ def drive
48
+ @drive ||= DriveClient.new(method(:call))
49
+ end
50
+
51
+ # Generic proxy call for any provider action.
52
+ #
53
+ # @param provider [String] integration provider name (e.g. "gmail")
54
+ # @param action [String] action to perform (e.g. "list-messages")
55
+ # @param params [Hash, nil] optional request body parameters
56
+ # @return [Object] the "data" field from the platform response
57
+ # @raise [Leash::NotConnectedError] if the provider is not connected
58
+ # @raise [Leash::TokenExpiredError] if the OAuth token has expired
59
+ # @raise [Leash::Error] if the platform returns a non-success response
60
+ def call(provider, action, params = nil)
61
+ uri = URI("#{@platform_url}/api/integrations/#{provider}/#{action}")
62
+
63
+ request = Net::HTTP::Post.new(uri)
64
+ request["Content-Type"] = "application/json"
65
+ request["Authorization"] = "Bearer #{@auth_token}"
66
+ request.body = (params || {}).to_json
67
+
68
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
69
+ http.request(request)
70
+ end
71
+
72
+ data = JSON.parse(response.body)
73
+
74
+ unless data["success"]
75
+ raise_error(data)
76
+ end
77
+
78
+ data["data"]
79
+ end
80
+
81
+ # Check if a provider is connected for the current user.
82
+ #
83
+ # @param provider_id [String] the provider identifier (e.g. "gmail")
84
+ # @return [Boolean]
85
+ def connected?(provider_id)
86
+ conn = connections.find { |c| c["providerId"] == provider_id }
87
+ conn&.dig("status") == "active"
88
+ rescue StandardError
89
+ false
90
+ end
91
+
92
+ # Get connection status for all providers.
93
+ #
94
+ # @return [Array<Hash>] list of connection status hashes
95
+ def connections
96
+ uri = URI("#{@platform_url}/api/integrations/connections")
97
+
98
+ request = Net::HTTP::Get.new(uri)
99
+ request["Authorization"] = "Bearer #{@auth_token}"
100
+
101
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
102
+ http.request(request)
103
+ end
104
+
105
+ data = JSON.parse(response.body)
106
+
107
+ unless data["success"]
108
+ raise_error(data)
109
+ end
110
+
111
+ data["data"] || []
112
+ end
113
+
114
+ # Get the URL to connect a provider (for UI buttons).
115
+ #
116
+ # @param provider_id [String] the provider identifier
117
+ # @param return_url [String, nil] optional URL to redirect back to after connecting
118
+ # @return [String] the full URL to initiate the OAuth connection flow
119
+ def connect_url(provider_id, return_url: nil)
120
+ url = "#{@platform_url}/api/integrations/connect/#{provider_id}"
121
+ url += "?return_url=#{URI.encode_www_form_component(return_url)}" if return_url
122
+ url
123
+ end
124
+
125
+ private
126
+
127
+ # Map error codes to specific exception classes.
128
+ #
129
+ # @param data [Hash] the parsed error response
130
+ # @raise [Leash::NotConnectedError, Leash::TokenExpiredError, Leash::Error]
131
+ def raise_error(data)
132
+ message = data["error"] || "Unknown error"
133
+ code = data["code"]
134
+ connect_url = data["connectUrl"]
135
+
136
+ case code
137
+ when "not_connected"
138
+ raise NotConnectedError.new(message, connect_url: connect_url)
139
+ when "token_expired"
140
+ raise TokenExpiredError.new(message, connect_url: connect_url)
141
+ else
142
+ raise Error.new(message, code: code, connect_url: connect_url)
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/leash.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "leash/integrations"
4
+
5
+ module Leash
6
+ VERSION = "0.1.0"
7
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leash-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leash
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Access Gmail, Google Calendar, Google Drive, and more through the Leash
14
+ platform proxy. No API keys needed -- uses your Leash auth token.
15
+ email:
16
+ - hello@leash.build
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - Gemfile
22
+ - LICENSE
23
+ - README.md
24
+ - leash-sdk.gemspec
25
+ - lib/leash.rb
26
+ - lib/leash/calendar.rb
27
+ - lib/leash/drive.rb
28
+ - lib/leash/errors.rb
29
+ - lib/leash/gmail.rb
30
+ - lib/leash/integrations.rb
31
+ homepage: https://github.com/leash-build/leash-sdk-ruby
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ homepage_uri: https://github.com/leash-build/leash-sdk-ruby
36
+ source_code_uri: https://github.com/leash-build/leash-sdk-ruby
37
+ changelog_uri: https://github.com/leash-build/leash-sdk-ruby/blob/main/CHANGELOG.md
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubygems_version: 3.0.3.1
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: Ruby SDK for the Leash platform integrations API
57
+ test_files: []