bundleup-sdk 0.4.1 → 0.4.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e2898652fed3548927b24223483a9308f826e2899503c4eb711275d9418145a
4
- data.tar.gz: ec2179acf284beb8c8e94a1079c671ee2a3a5218905d9187648b62120bcbcaa4
3
+ metadata.gz: 6a81131b46ad1f04a1714e8e7a07d87d69ee3419648e966daa9099cba31fb4a9
4
+ data.tar.gz: b1861d43ace1fd90f86e89e8b161b91c135f1fc53215cf0dad3defd95351a048
5
5
  SHA512:
6
- metadata.gz: 4bf6f9e0912c4cefbc510011e4475743517c21fe324c428ccb8e2b44a24104c2fa3299069edcafca8cf1c47879d6620792fada35af230414f34e138dd5742990
7
- data.tar.gz: 5dce15658d4a193bc89771b8726a1e5faea4326f7c20c76ed7c1b23a9e4eef01c1bf30e7ec785a2f478e2610fc193078f8c1d0adca4ecda0557a7846d6b07963
6
+ metadata.gz: 13c18248fe861fb462cf3d9019764d13a7925031b49aa40d37f5ab7c3b106e019407140873dda6467057777313bc58cc6cb661daac8f102ff462b318aec4c413
7
+ data.tar.gz: c3bc826ddfb41e39616049a651b180dffd36bb3ee939f27ee315bd350400ae25dbec72666025f22860944d20f82e1945dc20f856b7a2c2cc0302cb671b7b0f9f
data/README.md CHANGED
@@ -83,7 +83,7 @@ Runnable examples are available in the [`examples/`](./examples) directory:
83
83
 
84
84
  - [`examples/basic_usage.rb`](./examples/basic_usage.rb) - Client setup, connections, integrations, and webhooks
85
85
  - [`examples/proxy_api.rb`](./examples/proxy_api.rb) - Proxy API GET request with a connection
86
- - [`examples/unify_api.rb`](./examples/unify_api.rb) - Unify Chat, Git, PM, Ticketing, and Drive endpoint usage
86
+ - [`examples/unify_api.rb`](./examples/unify_api.rb) - Unify Chat, Git, PM, Ticketing, Drive, and Calendar endpoint usage
87
87
  - [`examples/README.md`](./examples/README.md) - Setup and execution instructions
88
88
 
89
89
  ## Quick Start
@@ -146,7 +146,7 @@ client = BundleUp::Client.new(ENV['BUNDLEUP_API_KEY'])
146
146
  **Example `.env` file:**
147
147
 
148
148
  ```bash
149
- BUNDLEUP_API_KEY=bu_live_1234567890abcdefghijklmnopqrstuvwxyz
149
+ BUNDLEUP_API_KEY=Zw7Lt7JacsDyMCEpnZdGptgnJaOdMzFVH9QtIthnL5RviYP5WeH6e9FWP2HzEO
150
150
  ```
151
151
 
152
152
  **Loading environment variables (using dotenv):**
@@ -737,6 +737,35 @@ end
737
737
  puts "Fetched #{all_channels.length} total channels"
738
738
  ```
739
739
 
740
+ ##### List Messages
741
+
742
+ Messages in one channel, newest first. `author.name` is nil on Slack, which returns only a user id on a message.
743
+
744
+ ```ruby
745
+ result = unify.chat.messages('C123', limit: 100, after: nil, include_raw: false)
746
+
747
+ puts "Messages: #{result['data']}"
748
+ ```
749
+
750
+ **Response:**
751
+
752
+ ```ruby
753
+ {
754
+ 'data' => [
755
+ {
756
+ 'id' => '1755712345.123456',
757
+ 'text' => 'Deploy finished',
758
+ 'author' => { 'id' => 'U024BE7LH', 'name' => nil },
759
+ 'created_at' => '2026-08-20T18:32:25.123Z',
760
+ 'thread_id' => nil
761
+ }
762
+ ],
763
+ 'metadata' => {
764
+ 'next' => 'cursor_def456'
765
+ }
766
+ }
767
+ ```
768
+
740
769
  ##### Send Message
741
770
 
742
771
  Send a message to a channel on the connected chat platform.
@@ -971,6 +1000,34 @@ open_tickets = result['data'].select { |ticket| ticket['status'] == 'open' }
971
1000
  sorted_by_date = result['data'].sort_by { |ticket| Time.parse(ticket['created_at']) }.reverse
972
1001
  ```
973
1002
 
1003
+ ##### Get a Ticket
1004
+
1005
+ Fetch one ticket by ID. Not supported by Basecamp, whose API only serves a to-do underneath its project.
1006
+
1007
+ ```ruby
1008
+ result = unify.ticketing.ticket('PROJ-123')
1009
+
1010
+ puts "Ticket: #{result['data']}"
1011
+ ```
1012
+
1013
+ **Response:**
1014
+
1015
+ ```ruby
1016
+ {
1017
+ 'data' => {
1018
+ 'id' => 'PROJ-123',
1019
+ 'url' => 'https://jira.example.com/browse/PROJ-123',
1020
+ 'title' => 'Fix login bug',
1021
+ 'status' => 'in_progress',
1022
+ 'description' => 'Users are unable to log in',
1023
+ 'created_at' => '2024-01-15T10:30:00Z',
1024
+ 'updated_at' => '2024-01-20T14:22:00Z'
1025
+ }
1026
+ }
1027
+ ```
1028
+
1029
+ A single resource carries no pagination, so there is no `metadata` on this response.
1030
+
974
1031
  #### CRM API
975
1032
 
976
1033
  The CRM API provides a unified interface for CRM platforms like Attio, HubSpot, PipeDrive, Salesforce and Zoho.
@@ -1071,6 +1128,49 @@ puts "Files: #{result['data']}"
1071
1128
  }
1072
1129
  ```
1073
1130
 
1131
+ #### Calendar API
1132
+
1133
+ The Calendar API provides a unified interface for calendar and scheduling platforms like Google Calendar, Outlook, Calendly and Zoom.
1134
+
1135
+ ##### List Events
1136
+
1137
+ `starts_after` and `starts_before` are required — the endpoint refuses an unbounded listing.
1138
+
1139
+ ```ruby
1140
+ result = unify.calendar.events(
1141
+ starts_after: '2026-09-01T00:00:00Z',
1142
+ starts_before: '2026-09-08T00:00:00Z',
1143
+ limit: 100,
1144
+ after: nil,
1145
+ include_raw: false
1146
+ )
1147
+
1148
+ puts "Events: #{result['data']}"
1149
+ ```
1150
+
1151
+ **Response:**
1152
+
1153
+ ```ruby
1154
+ {
1155
+ 'data' => [
1156
+ {
1157
+ 'id' => 'evt_123',
1158
+ 'title' => 'Design review',
1159
+ 'description' => 'Walk through the new onboarding flow',
1160
+ 'start_date' => '2026-09-01T15:00:00Z',
1161
+ 'end_date' => '2026-09-01T16:00:00Z',
1162
+ 'status' => 'confirmed',
1163
+ 'url' => 'https://calendar.google.com/event?eid=...'
1164
+ }
1165
+ ],
1166
+ 'metadata' => {
1167
+ 'next' => nil
1168
+ }
1169
+ }
1170
+ ```
1171
+
1172
+ Recurring events are expanded into their occurrences. All-day events carry a `YYYY-MM-DD` date rather than a timestamp. Attendees, conferencing links and organizers are available through `include_raw` or the Proxy API.
1173
+
1074
1174
  ## Error Handling
1075
1175
 
1076
1176
  The SDK raises exceptions for errors. Always wrap SDK calls in rescue blocks for proper error handling.
@@ -1126,7 +1226,8 @@ lib/
1126
1226
  │ ├── git.rb # Git Unify API
1127
1227
  │ ├── ticketing.rb # Ticketing Unify API
1128
1228
  │ ├── crm.rb # CRM Unify API
1129
- │ └── drive.rb # Drive Unify API
1229
+ │ ├── drive.rb # Drive Unify API
1230
+ │ └── calendar.rb # Calendar Unify API
1130
1231
  sig/ # RBS type signatures (mirrors lib/)
1131
1232
  spec/ # Test files
1132
1233
  ```
data/lib/bundleup/mcp.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require_relative 'mcp_client'
5
4
 
6
5
  module BundleUp
7
6
  # Transport for a connection's MCP server.
@@ -68,4 +67,201 @@ module BundleUp
68
67
  @connection ||= Faraday.new { |faraday| faraday.adapter Faraday.default_adapter }
69
68
  end
70
69
  end
70
+
71
+ # A connected MCP session.
72
+ #
73
+ # Tools, resources and prompts are defined by the provider — BundleUp does
74
+ # not rename or normalize them.
75
+ class MCPClient
76
+ PROTOCOL_VERSION = '2025-06-18'
77
+ CLIENT_NAME = 'bundleup-sdk'
78
+
79
+ def initialize(base_url, api_key, connection_id)
80
+ @base_url = base_url
81
+ @api_key = api_key
82
+ @connection_id = connection_id
83
+ @session_id = nil
84
+ @connected = false
85
+ @last_id = 0
86
+ end
87
+
88
+ # List the provider's tools, following pagination to the end.
89
+ def list_tools
90
+ paginate('tools/list', 'tools')
91
+ end
92
+
93
+ # Call a tool by name, with arguments matching its own input schema.
94
+ def call_tool(name, args = {})
95
+ raise ArgumentError, 'Tool name is required to call a tool.' if blank?(name)
96
+
97
+ connect
98
+ send_message('tools/call', { name: name, arguments: args })
99
+ end
100
+
101
+ # List the provider's resources, following pagination to the end.
102
+ def list_resources
103
+ paginate('resources/list', 'resources')
104
+ end
105
+
106
+ # Read a resource by URI.
107
+ def read_resource(uri)
108
+ raise ArgumentError, 'Resource URI is required to read a resource.' if blank?(uri)
109
+
110
+ connect
111
+ send_message('resources/read', { uri: uri })
112
+ end
113
+
114
+ # List the provider's prompts, following pagination to the end.
115
+ def list_prompts
116
+ paginate('prompts/list', 'prompts')
117
+ end
118
+
119
+ # Get a prompt by name.
120
+ def get_prompt(name, args = {})
121
+ raise ArgumentError, 'Prompt name is required to get a prompt.' if blank?(name)
122
+
123
+ connect
124
+ send_message('prompts/get', { name: name, arguments: args })
125
+ end
126
+
127
+ # Send any other JSON-RPC method on this session.
128
+ def request(method, params = nil)
129
+ raise ArgumentError, 'Method is required to send a request.' if blank?(method)
130
+
131
+ connect
132
+ send_message(method, params)
133
+ end
134
+
135
+ # End the session and reset local state.
136
+ def close
137
+ delete_session if @session_id
138
+
139
+ @session_id = nil
140
+ @connected = false
141
+ nil
142
+ end
143
+
144
+ private
145
+
146
+ def blank?(value)
147
+ value.nil? || value.to_s.empty?
148
+ end
149
+
150
+ def default_headers
151
+ headers = {
152
+ 'Authorization' => "Bearer #{@api_key}",
153
+ 'Content-Type' => 'application/json',
154
+ 'Accept' => 'application/json, text/event-stream',
155
+ 'BU-Connection-Id' => @connection_id
156
+ }
157
+ headers['Mcp-Session-Id'] = @session_id if @session_id
158
+ headers
159
+ end
160
+
161
+ def connection
162
+ @connection ||= Faraday.new { |faraday| faraday.adapter Faraday.default_adapter }
163
+ end
164
+
165
+ def post_payload(payload)
166
+ response = connection.post(@base_url, payload.to_json, default_headers)
167
+ session_id = response.headers['mcp-session-id']
168
+ @session_id = session_id if session_id
169
+
170
+ raise error_for(response) unless response.success?
171
+
172
+ response
173
+ end
174
+
175
+ def error_for(response)
176
+ fallback = "MCP request failed with status #{response.status}."
177
+ parsed = JSON.parse(response.body.to_s)
178
+ return RuntimeError.new(fallback) unless parsed.is_a?(Hash) && parsed['message']
179
+
180
+ code = parsed['code']
181
+ RuntimeError.new(code ? "#{parsed['message']} (#{code})" : parsed['message'])
182
+ rescue JSON::ParserError
183
+ RuntimeError.new(fallback)
184
+ end
185
+
186
+ # Run the MCP handshake, once. Deferred until the first call.
187
+ def connect
188
+ return if @connected
189
+
190
+ send_message('initialize', handshake_params)
191
+ post_payload({ jsonrpc: '2.0', method: 'notifications/initialized' })
192
+ @connected = true
193
+ end
194
+
195
+ def handshake_params
196
+ {
197
+ protocolVersion: PROTOCOL_VERSION,
198
+ capabilities: {},
199
+ clientInfo: { name: CLIENT_NAME, version: BundleUp::VERSION }
200
+ }
201
+ end
202
+
203
+ def send_message(method, params = nil)
204
+ @last_id += 1
205
+ payload = { jsonrpc: '2.0', id: @last_id, method: method }
206
+ payload[:params] = params unless params.nil?
207
+
208
+ message = parse(post_payload(payload), @last_id)
209
+ raise "No response received for #{method}." if message.nil?
210
+ raise message['error']['message'].to_s if message['error']
211
+
212
+ message['result'] || {}
213
+ end
214
+
215
+ # Providers may answer a plain request/response over text/event-stream.
216
+ def parse(response, message_id)
217
+ body = response.body.to_s
218
+ return nil if body.empty?
219
+
220
+ content_type = response.headers['content-type'].to_s
221
+ return JSON.parse(body) unless content_type.include?('text/event-stream')
222
+
223
+ parse_stream(body, message_id)
224
+ end
225
+
226
+ def parse_stream(body, message_id)
227
+ body.gsub("\r\n", "\n").split("\n\n").each do |event|
228
+ data = event_data(event)
229
+ next if data.empty?
230
+
231
+ message = JSON.parse(data)
232
+ # Skip server notifications interleaved on the stream.
233
+ return message if message.is_a?(Hash) && message['id'] == message_id
234
+ end
235
+
236
+ nil
237
+ end
238
+
239
+ def event_data(event)
240
+ event.split("\n")
241
+ .select { |line| line.start_with?('data:') }
242
+ .map { |line| line.sub('data:', '').strip }
243
+ .join("\n")
244
+ end
245
+
246
+ def paginate(method, key)
247
+ connect
248
+ items = []
249
+ cursor = nil
250
+
251
+ loop do
252
+ result = send_message(method, cursor ? { cursor: cursor } : nil)
253
+ items.concat(result[key] || [])
254
+ cursor = result['nextCursor']
255
+ break unless cursor
256
+ end
257
+
258
+ items
259
+ end
260
+
261
+ def delete_session
262
+ connection.delete(@base_url, nil, default_headers)
263
+ rescue Faraday::Error
264
+ nil
265
+ end
266
+ end
71
267
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleUp
4
+ module Unify
5
+ # Client for Calendar-related Unify endpoints.
6
+ class Calendar < Base
7
+ # Fetches events from the connected calendar provider.
8
+ #
9
+ # +params+ must carry +starts_after+ and +starts_before+ (ISO 8601) —
10
+ # the endpoint refuses an unbounded listing.
11
+ def events(params = {})
12
+ raise ArgumentError, 'starts_after and starts_before are required to fetch events.' if window_missing?(params)
13
+
14
+ response = connection.get('calendar/events') do |req|
15
+ req.params = params
16
+ end
17
+
18
+ raise "Failed to fetch calendar/events: #{response.status}" unless response.success?
19
+
20
+ response.body
21
+ end
22
+
23
+ private
24
+
25
+ def window_missing?(params)
26
+ params ||= {}
27
+
28
+ %i[starts_after starts_before].any? do |key|
29
+ value = params[key] || params[key.to_s]
30
+ value.nil? || value.to_s.empty?
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -26,6 +26,26 @@ module BundleUp
26
26
  response.body
27
27
  end
28
28
 
29
+ # Fetches messages in a channel from the connected chat provider.
30
+ #
31
+ # Newest first. +author.name+ is nil on Slack, which returns only a user
32
+ # id on a message.
33
+ def messages(channel_id, params = {})
34
+ raise ArgumentError, 'channel_id is required to fetch messages.' if channel_id.nil?
35
+
36
+ encoded_channel_id = URI.encode_www_form_component(channel_id)
37
+
38
+ response = connection.get("chat/channels/#{encoded_channel_id}/messages") do |req|
39
+ req.params = params
40
+ end
41
+
42
+ unless response.success?
43
+ raise "Failed to fetch chat/channels/#{encoded_channel_id}/messages: #{response.status}"
44
+ end
45
+
46
+ response.body
47
+ end
48
+
29
49
  # Sends a message to a channel on the connected chat provider.
30
50
  def message(channel_id, text)
31
51
  raise ArgumentError, 'channel_id is required to send a message.' if channel_id.nil?
@@ -29,15 +29,15 @@ module BundleUp
29
29
  end
30
30
 
31
31
  # List the available unified tools.
32
- def tools
33
- @client.tools
32
+ def list_tools
33
+ @client.list_tools
34
34
  end
35
35
 
36
36
  # Call a unified tool with optional arguments.
37
- def tool(name, args = {})
37
+ def call_tool(name, args = {})
38
38
  raise ArgumentError, 'Tool name is required to call a tool.' if name.nil? || name.to_s.empty?
39
39
 
40
- @client.tool(name, args)
40
+ @client.call_tool(name, args)
41
41
  end
42
42
  end
43
43
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundleUp
4
+ module Unify
5
+ # Client for the root-level identity endpoint.
6
+ #
7
+ # `me` is the one unified method every provider implements, so the API mounts
8
+ # it at the root rather than under a vertical. It is exposed on the Unify
9
+ # client as `unify.me` rather than as a namespace.
10
+ class Me < Base
11
+ # Fetches the account the connection is authenticated as.
12
+ #
13
+ # Providers that authorize per workspace, portal, tenant or company return
14
+ # that account instead of a user, and fields the provider does not expose
15
+ # come back as nil.
16
+ def get(params = {})
17
+ response = connection.get('me') do |req|
18
+ req.params = params
19
+ end
20
+
21
+ raise "Failed to fetch me: #{response.status}" unless response.success?
22
+
23
+ response.body
24
+ end
25
+ end
26
+ end
27
+ end
@@ -14,6 +14,30 @@ module BundleUp
14
14
 
15
15
  response.body
16
16
  end
17
+
18
+ # Fetches a single ticket by ID from the connected ticketing tool.
19
+ #
20
+ # Not supported by Basecamp, whose API only serves a to-do underneath its
21
+ # project — an id on its own cannot address one.
22
+ def ticket(ticket_id, params = {})
23
+ raise ArgumentError, 'ticket_id is required to fetch a ticket.' if blank?(ticket_id)
24
+
25
+ encoded_id = URI.encode_www_form_component(ticket_id)
26
+
27
+ response = connection.get("ticketing/tickets/#{encoded_id}") do |req|
28
+ req.params = params
29
+ end
30
+
31
+ raise "Failed to fetch ticketing/tickets/#{encoded_id}: #{response.status}" unless response.success?
32
+
33
+ response.body
34
+ end
35
+
36
+ private
37
+
38
+ def blank?(value)
39
+ value.nil? || value.to_s.empty?
40
+ end
17
41
  end
18
42
  end
19
43
  end
@@ -12,6 +12,7 @@ module BundleUp
12
12
  @git = BundleUp::Unify::Git.new(api_key, connection_id)
13
13
  @crm = BundleUp::Unify::CRM.new(api_key, connection_id)
14
14
  @drive = BundleUp::Unify::Drive.new(api_key, connection_id)
15
+ @calendar = BundleUp::Unify::Calendar.new(api_key, connection_id)
15
16
  @api_key = api_key
16
17
  @connection_id = connection_id
17
18
  end
@@ -41,6 +42,19 @@ module BundleUp
41
42
  @drive ||= BundleUp::Unify::Drive.new(api_key, connection_id)
42
43
  end
43
44
 
45
+ # Access the Calendar API for the connection.
46
+ def calendar
47
+ @calendar ||= BundleUp::Unify::Calendar.new(api_key, connection_id)
48
+ end
49
+
50
+ # Fetch the account this connection is authenticated as.
51
+ #
52
+ # `me` is the one unified method every provider implements, so it hangs off
53
+ # the Unify client directly instead of a vertical namespace.
54
+ def me(params = {})
55
+ (@me ||= BundleUp::Unify::Me.new(api_key, connection_id)).get(params)
56
+ end
57
+
44
58
  # Access the Unified MCP server for the connection.
45
59
  def mcp
46
60
  @mcp ||= BundleUp::Unify::MCP.new(api_key, connection_id)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BundleUp
4
- VERSION = '0.4.1'
4
+ VERSION = '0.4.3'
5
5
  end
data/lib/bundleup.rb CHANGED
@@ -21,7 +21,9 @@ require_relative 'bundleup/unify/git'
21
21
  require_relative 'bundleup/unify/ticketing'
22
22
  require_relative 'bundleup/unify/crm'
23
23
  require_relative 'bundleup/unify/drive'
24
+ require_relative 'bundleup/unify/calendar'
24
25
  require_relative 'bundleup/unify/mcp'
26
+ require_relative 'bundleup/unify/me'
25
27
 
26
28
  # Main module for the BundleUp SDK.
27
29
  module BundleUp
@@ -0,0 +1,23 @@
1
+ module BundleUp
2
+ module Unify
3
+ class Calendar < Base
4
+ type calendar_event = {
5
+ id: String,
6
+ title: String?,
7
+ description: String?,
8
+ start_date: String?,
9
+ end_date: String?,
10
+ status: String?,
11
+ url: String?
12
+ }
13
+
14
+ type metadata = { next: String? }
15
+
16
+ def events: (?Hash[Symbol, untyped] params) -> { data: Array[calendar_event], metadata: metadata }
17
+
18
+ private
19
+
20
+ def window_missing?: (Hash[Symbol, untyped]?) -> bool
21
+ end
22
+ end
23
+ end
@@ -3,10 +3,21 @@ module BundleUp
3
3
  class Chat < Base
4
4
  type chat_user = { id: String, name: String }
5
5
  type chat_channel = { id: String, name: String }
6
+ type chat_author = { id: String?, name: String? }
7
+
8
+ type chat_message = {
9
+ id: String,
10
+ text: String?,
11
+ author: chat_author?,
12
+ created_at: String?,
13
+ thread_id: String?
14
+ }
15
+
6
16
  type metadata = { next: String? }
7
17
 
8
18
  def users: (?Hash[Symbol, untyped] params) -> { data: Array[chat_user], metadata: metadata }
9
19
  def channels: (?Hash[Symbol, untyped] params) -> { data: Array[chat_channel], metadata: metadata }
20
+ def messages: (String channel_id, ?Hash[Symbol, untyped] params) -> { data: Array[chat_message], metadata: metadata }
10
21
  def message: (String channel_id, String text) -> { data: Hash[String, untyped] }
11
22
  end
12
23
  end
@@ -0,0 +1,14 @@
1
+ module BundleUp
2
+ module Unify
3
+ class Me < Base
4
+ type account = {
5
+ id: String?,
6
+ name: String?,
7
+ email: String?,
8
+ avatar_url: String?
9
+ }
10
+
11
+ def get: (?Hash[Symbol, untyped] params) -> { data: account }
12
+ end
13
+ end
14
+ end
@@ -11,9 +11,25 @@ module BundleUp
11
11
  updated_at: String
12
12
  }
13
13
 
14
+ type ticket = {
15
+ id: String,
16
+ title: String,
17
+ status: String?,
18
+ url: String?,
19
+ description: String?,
20
+ created_at: String?,
21
+ updated_at: String?
22
+ }
23
+
14
24
  type metadata = { next: String? }
15
25
 
16
- def issues: (?Hash[Symbol, untyped] params) -> { data: Array[ticketing_ticket], metadata: metadata }
26
+ def tickets: (?Hash[Symbol, untyped] params) -> { data: Array[ticketing_ticket], metadata: metadata }
27
+
28
+ def ticket: (String ticket_id, ?Hash[Symbol, untyped] params) -> { data: ticket }
29
+
30
+ private
31
+
32
+ def blank?: (untyped) -> bool
17
33
  end
18
34
  end
19
35
  end
@@ -11,6 +11,8 @@ module BundleUp
11
11
  def pm: () -> PM
12
12
  def crm: () -> CRM
13
13
  def drive: () -> Drive
14
+ def calendar: () -> Calendar
15
+ def me: (?Hash[Symbol, untyped] params) -> { data: Me::account }
14
16
  end
15
17
  end
16
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundleup-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - BundleUp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-25 00:00:00.000000000 Z
11
+ date: 2026-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -177,19 +177,23 @@ files:
177
177
  - lib/bundleup/resources/webhook.rb
178
178
  - lib/bundleup/unify.rb
179
179
  - lib/bundleup/unify/base.rb
180
+ - lib/bundleup/unify/calendar.rb
180
181
  - lib/bundleup/unify/chat.rb
181
182
  - lib/bundleup/unify/crm.rb
182
183
  - lib/bundleup/unify/drive.rb
183
184
  - lib/bundleup/unify/git.rb
184
185
  - lib/bundleup/unify/mcp.rb
186
+ - lib/bundleup/unify/me.rb
185
187
  - lib/bundleup/unify/ticketing.rb
186
188
  - lib/bundleup/version.rb
187
189
  - sig/bundleup/unify.rbs
188
190
  - sig/bundleup/unify/base.rbs
191
+ - sig/bundleup/unify/calendar.rbs
189
192
  - sig/bundleup/unify/chat.rbs
190
193
  - sig/bundleup/unify/crm.rbs
191
194
  - sig/bundleup/unify/drive.rbs
192
195
  - sig/bundleup/unify/git.rbs
196
+ - sig/bundleup/unify/me.rbs
193
197
  - sig/bundleup/unify/ticketing.rbs
194
198
  homepage: https://github.com/bundleup/bundleup-sdk-ruby
195
199
  licenses: