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 +7 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +140 -0
- data/leash-sdk.gemspec +25 -0
- data/lib/leash/calendar.rb +73 -0
- data/lib/leash/drive.rb +48 -0
- data/lib/leash/errors.rb +34 -0
- data/lib/leash/gmail.rb +70 -0
- data/lib/leash/integrations.rb +146 -0
- data/lib/leash.rb +7 -0
- metadata +57 -0
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
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
|
data/lib/leash/drive.rb
ADDED
|
@@ -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
|
data/lib/leash/errors.rb
ADDED
|
@@ -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
|
data/lib/leash/gmail.rb
ADDED
|
@@ -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
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: []
|