bundleup-sdk 0.2.2 → 0.4.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 +4 -4
- data/README.md +208 -43
- data/lib/bundleup/client.rb +8 -0
- data/lib/bundleup/mcp.rb +267 -0
- data/lib/bundleup/unify/chat.rb +24 -0
- data/lib/bundleup/unify/crm.rb +30 -0
- data/lib/bundleup/unify/drive.rb +19 -0
- data/lib/bundleup/unify/git.rb +42 -0
- data/lib/bundleup/unify/mcp.rb +44 -0
- data/lib/bundleup/unify/ticketing.rb +19 -0
- data/lib/bundleup/unify.rb +35 -1
- data/lib/bundleup/version.rb +1 -1
- data/lib/bundleup.rb +5 -1
- data/sig/bundleup/unify/base.rbs +17 -0
- data/sig/bundleup/unify/chat.rbs +13 -0
- data/sig/bundleup/unify/crm.rbs +12 -0
- data/sig/bundleup/unify/drive.rbs +20 -0
- data/sig/bundleup/unify/git.rbs +53 -0
- data/sig/bundleup/unify/ticketing.rbs +19 -0
- data/sig/bundleup/unify.rbs +16 -0
- metadata +42 -3
- data/lib/bundleup/unify/pm.rb +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ece90ca061e8b5bbac827ca07dcc35b1c029e235d27ec319cef3117cd0652d0
|
|
4
|
+
data.tar.gz: 034fdb9a5ccf8c36aa38a3d8a7870f8c32a7997ff617cfed395fd4e0ff03aaae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb30ce08198acee62b5ec10469a4a116a8186eab6669fbc1bcaece16899911ca50f5b37a1bc15d6dcc096a87b6c160faa3ea2319c8ce552159aa4347be869b85
|
|
7
|
+
data.tar.gz: 38855ea19f0662f62acbddc68e0b439f4171dadf5bba56f29b4dde40c4cfc14f72cf6da871af0faa9039b6a37c5b3188a968bacd3abcb859468053913bd2efe6
|
data/README.md
CHANGED
|
@@ -24,7 +24,6 @@ Official Ruby SDK for the [BundleUp](https://bundleup.io) API. Connect to 100+ i
|
|
|
24
24
|
- [Development](#development)
|
|
25
25
|
- [Contributing](#contributing)
|
|
26
26
|
- [License](#license)
|
|
27
|
-
- [Support](#support)
|
|
28
27
|
|
|
29
28
|
## Installation
|
|
30
29
|
|
|
@@ -84,7 +83,7 @@ Runnable examples are available in the [`examples/`](./examples) directory:
|
|
|
84
83
|
|
|
85
84
|
- [`examples/basic_usage.rb`](./examples/basic_usage.rb) - Client setup, connections, integrations, and webhooks
|
|
86
85
|
- [`examples/proxy_api.rb`](./examples/proxy_api.rb) - Proxy API GET request with a connection
|
|
87
|
-
- [`examples/unify_api.rb`](./examples/unify_api.rb) - Unify Chat, Git, and
|
|
86
|
+
- [`examples/unify_api.rb`](./examples/unify_api.rb) - Unify Chat, Git, PM, Ticketing, and Drive endpoint usage
|
|
88
87
|
- [`examples/README.md`](./examples/README.md) - Setup and execution instructions
|
|
89
88
|
|
|
90
89
|
## Quick Start
|
|
@@ -641,6 +640,43 @@ unify = client.unify('conn_123abc')
|
|
|
641
640
|
|
|
642
641
|
The Chat API provides a unified interface for chat platforms like Slack, Discord, and Microsoft Teams.
|
|
643
642
|
|
|
643
|
+
##### List Users
|
|
644
|
+
|
|
645
|
+
Retrieve a list of users from the connected chat platform.
|
|
646
|
+
|
|
647
|
+
```ruby
|
|
648
|
+
result = unify.chat.users(
|
|
649
|
+
limit: 100,
|
|
650
|
+
after: nil,
|
|
651
|
+
include_raw: false
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
puts "Users: #{result['data']}"
|
|
655
|
+
puts "Next cursor: #{result['metadata']['next']}"
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
**Parameters:**
|
|
659
|
+
|
|
660
|
+
- `limit` (Integer, optional): Maximum number of users to return (default: 100, max: 1000)
|
|
661
|
+
- `after` (String, optional): Pagination cursor from previous response
|
|
662
|
+
- `include_raw` (Boolean, optional): Include raw API response from the integration (default: false)
|
|
663
|
+
|
|
664
|
+
**Response:**
|
|
665
|
+
|
|
666
|
+
```ruby
|
|
667
|
+
{
|
|
668
|
+
'data' => [
|
|
669
|
+
{
|
|
670
|
+
'id' => 'U1234567890',
|
|
671
|
+
'name' => 'Jane Doe'
|
|
672
|
+
}
|
|
673
|
+
],
|
|
674
|
+
'metadata' => {
|
|
675
|
+
'next' => 'cursor_abc123'
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
644
680
|
##### List Channels
|
|
645
681
|
|
|
646
682
|
Retrieve a list of channels from the connected chat platform.
|
|
@@ -701,6 +737,31 @@ end
|
|
|
701
737
|
puts "Fetched #{all_channels.length} total channels"
|
|
702
738
|
```
|
|
703
739
|
|
|
740
|
+
##### Send Message
|
|
741
|
+
|
|
742
|
+
Send a message to a channel on the connected chat platform.
|
|
743
|
+
|
|
744
|
+
```ruby
|
|
745
|
+
result = unify.chat.message('C1234567890', 'Hello from BundleUp! :wave:')
|
|
746
|
+
|
|
747
|
+
puts "Message sent: #{result['data']}"
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Parameters:**
|
|
751
|
+
|
|
752
|
+
- `channel_id` (String, required): The ID of the channel to send the message to
|
|
753
|
+
- `text` (String, required): Markdown-formatted message text
|
|
754
|
+
|
|
755
|
+
**Response:**
|
|
756
|
+
|
|
757
|
+
```ruby
|
|
758
|
+
{
|
|
759
|
+
'data' => {
|
|
760
|
+
# Raw response data from the chat provider
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
704
765
|
#### Git API
|
|
705
766
|
|
|
706
767
|
The Git API provides a unified interface for version control platforms like GitHub, GitLab, and Bitbucket.
|
|
@@ -841,20 +902,45 @@ puts "Releases: #{result['data']}"
|
|
|
841
902
|
}
|
|
842
903
|
```
|
|
843
904
|
|
|
844
|
-
|
|
905
|
+
##### List Branches
|
|
845
906
|
|
|
846
|
-
|
|
907
|
+
```ruby
|
|
908
|
+
result = unify.git.branches('organization/repo-name', limit: 50)
|
|
847
909
|
|
|
848
|
-
|
|
910
|
+
puts "Branches: #{result['data']}"
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**Response:**
|
|
849
914
|
|
|
850
915
|
```ruby
|
|
851
|
-
|
|
916
|
+
{
|
|
917
|
+
'data' => [
|
|
918
|
+
{
|
|
919
|
+
'name' => 'main',
|
|
920
|
+
'commit_sha' => 'abc123def456',
|
|
921
|
+
'protected' => true
|
|
922
|
+
}
|
|
923
|
+
],
|
|
924
|
+
'metadata' => {
|
|
925
|
+
'next' => nil
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
#### Ticketing API
|
|
931
|
+
|
|
932
|
+
The Ticketing API provides a unified interface for ticketing and project management platforms like Jira, Linear, and Asana.
|
|
933
|
+
|
|
934
|
+
##### List Tickets
|
|
935
|
+
|
|
936
|
+
```ruby
|
|
937
|
+
result = unify.ticketing.tickets(
|
|
852
938
|
limit: 100,
|
|
853
939
|
after: nil,
|
|
854
940
|
include_raw: false
|
|
855
941
|
)
|
|
856
942
|
|
|
857
|
-
puts "
|
|
943
|
+
puts "Tickets: #{result['data']}"
|
|
858
944
|
```
|
|
859
945
|
|
|
860
946
|
**Response:**
|
|
@@ -881,8 +967,108 @@ puts "Issues: #{result['data']}"
|
|
|
881
967
|
**Filtering and sorting:**
|
|
882
968
|
|
|
883
969
|
```ruby
|
|
884
|
-
|
|
885
|
-
sorted_by_date = result['data'].sort_by { |
|
|
970
|
+
open_tickets = result['data'].select { |ticket| ticket['status'] == 'open' }
|
|
971
|
+
sorted_by_date = result['data'].sort_by { |ticket| Time.parse(ticket['created_at']) }.reverse
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
#### CRM API
|
|
975
|
+
|
|
976
|
+
The CRM API provides a unified interface for CRM platforms like Attio, HubSpot, PipeDrive, Salesforce and Zoho.
|
|
977
|
+
|
|
978
|
+
##### List Companies
|
|
979
|
+
|
|
980
|
+
```ruby
|
|
981
|
+
result = unify.crm.companies(
|
|
982
|
+
limit: 100,
|
|
983
|
+
after: nil,
|
|
984
|
+
include_raw: false
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
puts "Companies: #{result['data']}"
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Response:**
|
|
991
|
+
|
|
992
|
+
```ruby
|
|
993
|
+
{
|
|
994
|
+
'data' => [
|
|
995
|
+
{
|
|
996
|
+
'id' => '12345',
|
|
997
|
+
'name' => 'Acme Inc.',
|
|
998
|
+
'website' => 'https://acme.example.com'
|
|
999
|
+
}
|
|
1000
|
+
],
|
|
1001
|
+
'metadata' => {
|
|
1002
|
+
'next' => nil
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
##### List Contacts
|
|
1008
|
+
|
|
1009
|
+
```ruby
|
|
1010
|
+
result = unify.crm.contacts(
|
|
1011
|
+
limit: 100,
|
|
1012
|
+
after: nil,
|
|
1013
|
+
include_raw: false
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
puts "Contacts: #{result['data']}"
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
**Response:**
|
|
1020
|
+
|
|
1021
|
+
```ruby
|
|
1022
|
+
{
|
|
1023
|
+
'data' => [
|
|
1024
|
+
{
|
|
1025
|
+
'id' => '67890',
|
|
1026
|
+
'name' => 'Jane Doe',
|
|
1027
|
+
'email' => 'jane@acme.example.com'
|
|
1028
|
+
}
|
|
1029
|
+
],
|
|
1030
|
+
'metadata' => {
|
|
1031
|
+
'next' => nil
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
#### Drive API
|
|
1037
|
+
|
|
1038
|
+
The Drive API provides a unified interface for file storage platforms like Google Drive, OneDrive, Box, Dropbox and Microsoft SharePoint.
|
|
1039
|
+
|
|
1040
|
+
##### List Files
|
|
1041
|
+
|
|
1042
|
+
```ruby
|
|
1043
|
+
result = unify.drive.files(
|
|
1044
|
+
limit: 100,
|
|
1045
|
+
after: nil,
|
|
1046
|
+
include_raw: false
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
puts "Files: #{result['data']}"
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
**Response:**
|
|
1053
|
+
|
|
1054
|
+
```ruby
|
|
1055
|
+
{
|
|
1056
|
+
'data' => [
|
|
1057
|
+
{
|
|
1058
|
+
'id' => 'file_123',
|
|
1059
|
+
'name' => 'quarterly-report.pdf',
|
|
1060
|
+
'mime_type' => 'application/pdf',
|
|
1061
|
+
'size' => 204800,
|
|
1062
|
+
'created_at' => '2024-01-15T10:30:00Z',
|
|
1063
|
+
'updated_at' => '2024-01-20T14:22:00Z',
|
|
1064
|
+
'url' => 'https://drive.example.com/file_123',
|
|
1065
|
+
'is_folder' => false
|
|
1066
|
+
}
|
|
1067
|
+
],
|
|
1068
|
+
'metadata' => {
|
|
1069
|
+
'next' => nil
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
886
1072
|
```
|
|
887
1073
|
|
|
888
1074
|
## Error Handling
|
|
@@ -938,10 +1124,22 @@ lib/
|
|
|
938
1124
|
│ ├── base.rb # Base Unify class
|
|
939
1125
|
│ ├── chat.rb # Chat Unify API
|
|
940
1126
|
│ ├── git.rb # Git Unify API
|
|
941
|
-
│
|
|
1127
|
+
│ ├── ticketing.rb # Ticketing Unify API
|
|
1128
|
+
│ ├── crm.rb # CRM Unify API
|
|
1129
|
+
│ └── drive.rb # Drive Unify API
|
|
1130
|
+
sig/ # RBS type signatures (mirrors lib/)
|
|
942
1131
|
spec/ # Test files
|
|
943
1132
|
```
|
|
944
1133
|
|
|
1134
|
+
### Type Signatures
|
|
1135
|
+
|
|
1136
|
+
The gem ships [RBS](https://github.com/ruby/rbs) signatures for the Unify API under `sig/bundleup/unify/`. They're optional at runtime (deleting `sig/` doesn't change behavior) but give editors and `rbs` itself static types for `unify.chat.channels`, `unify.git.repos`, and so on.
|
|
1137
|
+
|
|
1138
|
+
```bash
|
|
1139
|
+
# Validate the signature files
|
|
1140
|
+
bundle exec rake sig
|
|
1141
|
+
```
|
|
1142
|
+
|
|
945
1143
|
### Running Tests
|
|
946
1144
|
|
|
947
1145
|
```bash
|
|
@@ -1050,39 +1248,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
1050
1248
|
SOFTWARE.
|
|
1051
1249
|
```
|
|
1052
1250
|
|
|
1053
|
-
## Support
|
|
1054
|
-
|
|
1055
|
-
Need help? We're here for you!
|
|
1056
|
-
|
|
1057
|
-
### Documentation
|
|
1058
|
-
|
|
1059
|
-
- **Official Docs**: [https://docs.bundleup.io](https://docs.bundleup.io)
|
|
1060
|
-
- **API Reference**: [https://docs.bundleup.io/api](https://docs.bundleup.io/api)
|
|
1061
|
-
- **SDK Guides**: [https://docs.bundleup.io/sdk/ruby](https://docs.bundleup.io/sdk/ruby)
|
|
1062
|
-
|
|
1063
|
-
### Community
|
|
1064
|
-
|
|
1065
|
-
- **Discord**: [https://discord.gg/bundleup](https://discord.gg/bundleup)
|
|
1066
|
-
- **GitHub Discussions**: [https://github.com/bundleup/bundleup-sdk-ruby/discussions](https://github.com/bundleup/bundleup-sdk-ruby/discussions)
|
|
1067
|
-
- **Stack Overflow**: Tag your questions with `bundleup`
|
|
1068
|
-
|
|
1069
|
-
### Direct Support
|
|
1070
|
-
|
|
1071
|
-
- **Email**: [support@bundleup.io](mailto:support@bundleup.io)
|
|
1072
|
-
- **GitHub Issues**: [https://github.com/bundleup/bundleup-sdk-ruby/issues](https://github.com/bundleup/bundleup-sdk-ruby/issues)
|
|
1073
|
-
- **Twitter**: [@bundleup_io](https://twitter.com/bundleup_io)
|
|
1074
|
-
|
|
1075
|
-
### Enterprise Support
|
|
1076
|
-
|
|
1077
|
-
For enterprise customers, we offer:
|
|
1078
|
-
|
|
1079
|
-
- Priority support with SLA
|
|
1080
|
-
- Dedicated support channel
|
|
1081
|
-
- Architecture consultation
|
|
1082
|
-
- Custom integration assistance
|
|
1083
|
-
|
|
1084
|
-
Contact [enterprise@bundleup.io](mailto:enterprise@bundleup.io) for more information.
|
|
1085
|
-
|
|
1086
1251
|
## Code of Conduct
|
|
1087
1252
|
|
|
1088
1253
|
Everyone interacting in the BundleUp project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bundleup/bundleup-sdk-ruby/blob/main/CODE_OF_CONDUCT).
|
data/lib/bundleup/client.rb
CHANGED
|
@@ -38,5 +38,13 @@ module BundleUp
|
|
|
38
38
|
|
|
39
39
|
BundleUp::Unify::Client.new(@api_key, connection_id)
|
|
40
40
|
end
|
|
41
|
+
|
|
42
|
+
def mcp(connection_id)
|
|
43
|
+
if connection_id.nil? || connection_id.empty?
|
|
44
|
+
raise ArgumentError, 'Connection ID is required to create an MCP instance.'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
BundleUp::MCP.new(@api_key, connection_id)
|
|
48
|
+
end
|
|
41
49
|
end
|
|
42
50
|
end
|
data/lib/bundleup/mcp.rb
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module BundleUp
|
|
6
|
+
# Transport for a connection's MCP server.
|
|
7
|
+
#
|
|
8
|
+
# +post+ and +delete+ return the raw Faraday response, the way Proxy does.
|
|
9
|
+
# Use +connect+ for a managed session that handles the handshake and
|
|
10
|
+
# response decoding.
|
|
11
|
+
class MCP
|
|
12
|
+
BASE_URL = 'https://mcp.bundleup.io'
|
|
13
|
+
|
|
14
|
+
# Separates the API key from an appended connection ID. Safe to split on:
|
|
15
|
+
# API keys are alphanumeric and connection IDs are cuids.
|
|
16
|
+
CREDENTIAL_SEPARATOR = '.'
|
|
17
|
+
|
|
18
|
+
attr_reader :api_key, :connection_id
|
|
19
|
+
|
|
20
|
+
def initialize(api_key, connection_id)
|
|
21
|
+
@api_key = api_key
|
|
22
|
+
@connection_id = connection_id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The URL and headers for an MCP client running in your own process.
|
|
26
|
+
def transport
|
|
27
|
+
{ url: BASE_URL, headers: default_headers }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The URL and a single bearer token carrying both the API key and the
|
|
31
|
+
# connection, for model-hosted MCP clients that cannot set custom headers.
|
|
32
|
+
# Note this hands your API key to the model provider.
|
|
33
|
+
def hosted
|
|
34
|
+
{ url: BASE_URL, token: "#{@api_key}#{CREDENTIAL_SEPARATOR}#{@connection_id}" }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Send a JSON-RPC message and return the raw response. Pass
|
|
38
|
+
# +Mcp-Session-Id+ in +headers+ to stay on an existing session.
|
|
39
|
+
def post(body, headers: {})
|
|
40
|
+
payload = body.is_a?(String) ? body : body.to_json
|
|
41
|
+
|
|
42
|
+
connection.post(BASE_URL, payload, default_headers.merge(headers))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# End an MCP session. Pass the session's +Mcp-Session-Id+ in +headers+.
|
|
46
|
+
def delete(headers: {})
|
|
47
|
+
connection.delete(BASE_URL, nil, default_headers.merge(headers))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Open a managed MCP session for this connection.
|
|
51
|
+
def connect
|
|
52
|
+
MCPClient.new(BASE_URL, @api_key, @connection_id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def default_headers
|
|
58
|
+
{
|
|
59
|
+
'Authorization' => "Bearer #{@api_key}",
|
|
60
|
+
'Content-Type' => 'application/json',
|
|
61
|
+
'Accept' => 'application/json, text/event-stream',
|
|
62
|
+
'BU-Connection-Id' => @connection_id
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def connection
|
|
67
|
+
@connection ||= Faraday.new { |faraday| faraday.adapter Faraday.default_adapter }
|
|
68
|
+
end
|
|
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 tools
|
|
90
|
+
paginate('tools/list', 'tools')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Call a tool by name, with arguments matching its own input schema.
|
|
94
|
+
def 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 resources
|
|
103
|
+
paginate('resources/list', 'resources')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Read a resource by URI.
|
|
107
|
+
def 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 prompts
|
|
116
|
+
paginate('prompts/list', 'prompts')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Get a prompt by name.
|
|
120
|
+
def 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
|
|
267
|
+
end
|
data/lib/bundleup/unify/chat.rb
CHANGED
|
@@ -4,6 +4,17 @@ module BundleUp
|
|
|
4
4
|
module Unify
|
|
5
5
|
# Client for chat-related Unify endpoints.
|
|
6
6
|
class Chat < Base
|
|
7
|
+
# Fetches users from the connected chat provider.
|
|
8
|
+
def users(params = {})
|
|
9
|
+
response = connection.get('chat/users') do |req|
|
|
10
|
+
req.params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
raise "Failed to fetch chat/users: #{response.status}" unless response.success?
|
|
14
|
+
|
|
15
|
+
response.body
|
|
16
|
+
end
|
|
17
|
+
|
|
7
18
|
# Fetches channels from the connected chat provider.
|
|
8
19
|
def channels(params = {})
|
|
9
20
|
response = connection.get('chat/channels') do |req|
|
|
@@ -14,6 +25,19 @@ module BundleUp
|
|
|
14
25
|
|
|
15
26
|
response.body
|
|
16
27
|
end
|
|
28
|
+
|
|
29
|
+
# Sends a message to a channel on the connected chat provider.
|
|
30
|
+
def message(channel_id, text)
|
|
31
|
+
raise ArgumentError, 'channel_id is required to send a message.' if channel_id.nil?
|
|
32
|
+
|
|
33
|
+
encoded_channel_id = URI.encode_www_form_component(channel_id)
|
|
34
|
+
|
|
35
|
+
response = connection.post("chat/channels/#{encoded_channel_id}/message", { text: text }.to_json)
|
|
36
|
+
|
|
37
|
+
raise "Failed to post chat/channels/#{encoded_channel_id}/message: #{response.status}" unless response.success?
|
|
38
|
+
|
|
39
|
+
response.body
|
|
40
|
+
end
|
|
17
41
|
end
|
|
18
42
|
end
|
|
19
43
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BundleUp
|
|
4
|
+
module Unify
|
|
5
|
+
# Client for CRM-related Unify endpoints.
|
|
6
|
+
class CRM < Base
|
|
7
|
+
# Fetches companies from the connected CRM provider.
|
|
8
|
+
def companies(params = {})
|
|
9
|
+
response = connection.get('crm/companies') do |req|
|
|
10
|
+
req.params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
raise "Failed to fetch crm/companies: #{response.status}" unless response.success?
|
|
14
|
+
|
|
15
|
+
response.body
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Fetches contacts from the connected CRM provider.
|
|
19
|
+
def contacts(params = {})
|
|
20
|
+
response = connection.get('crm/contacts') do |req|
|
|
21
|
+
req.params = params
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
raise "Failed to fetch crm/contacts: #{response.status}" unless response.success?
|
|
25
|
+
|
|
26
|
+
response.body
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BundleUp
|
|
4
|
+
module Unify
|
|
5
|
+
# Client for Drive-related Unify endpoints.
|
|
6
|
+
class Drive < Base
|
|
7
|
+
# Fetches files from the connected Drive provider.
|
|
8
|
+
def files(params = {})
|
|
9
|
+
response = connection.get('drive/files') do |req|
|
|
10
|
+
req.params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
raise "Failed to fetch drive/files: #{response.status}" unless response.success?
|
|
14
|
+
|
|
15
|
+
response.body
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/bundleup/unify/git.rb
CHANGED
|
@@ -17,6 +17,8 @@ module BundleUp
|
|
|
17
17
|
|
|
18
18
|
# Fetches pull requests for a specific repository from the connected Git provider.
|
|
19
19
|
def pulls(repo_name, params = {})
|
|
20
|
+
raise ArgumentError, 'repo_name is required to fetch pulls.' if blank?(repo_name)
|
|
21
|
+
|
|
20
22
|
encoded_repo_name = URI.encode_www_form_component(repo_name)
|
|
21
23
|
|
|
22
24
|
response = connection.get("git/repos/#{encoded_repo_name}/pulls") do |req|
|
|
@@ -30,6 +32,8 @@ module BundleUp
|
|
|
30
32
|
|
|
31
33
|
# Fetches tags for a specific repository from the connected Git provider.
|
|
32
34
|
def tags(repo_name, params = {})
|
|
35
|
+
raise ArgumentError, 'repo_name is required to fetch tags.' if blank?(repo_name)
|
|
36
|
+
|
|
33
37
|
encoded_repo_name = URI.encode_www_form_component(repo_name)
|
|
34
38
|
|
|
35
39
|
response = connection.get("git/repos/#{encoded_repo_name}/tags") do |req|
|
|
@@ -43,6 +47,8 @@ module BundleUp
|
|
|
43
47
|
|
|
44
48
|
# Fetches releases for a specific repository from the connected Git provider.
|
|
45
49
|
def releases(repo_name, params = {})
|
|
50
|
+
raise ArgumentError, 'repo_name is required to fetch releases.' if blank?(repo_name)
|
|
51
|
+
|
|
46
52
|
encoded_repo_name = URI.encode_www_form_component(repo_name)
|
|
47
53
|
|
|
48
54
|
response = connection.get("git/repos/#{encoded_repo_name}/releases") do |req|
|
|
@@ -53,6 +59,42 @@ module BundleUp
|
|
|
53
59
|
|
|
54
60
|
response.body
|
|
55
61
|
end
|
|
62
|
+
|
|
63
|
+
# Fetches branches for a specific repository from the connected Git provider.
|
|
64
|
+
def branches(repo_name, params = {})
|
|
65
|
+
raise ArgumentError, 'repo_name is required to fetch branches.' if blank?(repo_name)
|
|
66
|
+
|
|
67
|
+
encoded_repo_name = URI.encode_www_form_component(repo_name)
|
|
68
|
+
|
|
69
|
+
response = connection.get("git/repos/#{encoded_repo_name}/branches") do |req|
|
|
70
|
+
req.params = params
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
raise "Failed to fetch git/repos/#{encoded_repo_name}/branches: #{response.status}" unless response.success?
|
|
74
|
+
|
|
75
|
+
response.body
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Fetches commits for a specific repository from the connected Git provider.
|
|
79
|
+
def commits(repo_name, params = {})
|
|
80
|
+
raise ArgumentError, 'repo_name is required to fetch commits.' if blank?(repo_name)
|
|
81
|
+
|
|
82
|
+
encoded_repo_name = URI.encode_www_form_component(repo_name)
|
|
83
|
+
|
|
84
|
+
response = connection.get("git/repos/#{encoded_repo_name}/commits") do |req|
|
|
85
|
+
req.params = params
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
raise "Failed to fetch git/repos/#{encoded_repo_name}/commits: #{response.status}" unless response.success?
|
|
89
|
+
|
|
90
|
+
response.body
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def blank?(value)
|
|
96
|
+
value.nil? || value.to_s.empty?
|
|
97
|
+
end
|
|
56
98
|
end
|
|
57
99
|
end
|
|
58
100
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BundleUp
|
|
4
|
+
module Unify
|
|
5
|
+
# The Unified MCP server.
|
|
6
|
+
#
|
|
7
|
+
# Same protocol and headers as Proxy MCP, but the tools are BundleUp's
|
|
8
|
+
# normalized ones rather than the provider's. Tools only — Unified MCP
|
|
9
|
+
# exposes no resources or prompts.
|
|
10
|
+
#
|
|
11
|
+
# The server is stateless and POST-only, so there is no session to close.
|
|
12
|
+
class MCP
|
|
13
|
+
BASE_URL = 'https://unify.bundleup.io/v1/mcp'
|
|
14
|
+
|
|
15
|
+
attr_reader :api_key, :connection_id
|
|
16
|
+
|
|
17
|
+
def initialize(api_key, connection_id)
|
|
18
|
+
@api_key = api_key
|
|
19
|
+
@connection_id = connection_id
|
|
20
|
+
@client = ::BundleUp::MCPClient.new(BASE_URL, api_key, connection_id)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The URL and a single bearer token carrying both the API key and the
|
|
24
|
+
# connection, for model-hosted MCP clients that cannot set headers.
|
|
25
|
+
def hosted
|
|
26
|
+
separator = ::BundleUp::MCP::CREDENTIAL_SEPARATOR
|
|
27
|
+
|
|
28
|
+
{ url: BASE_URL, token: "#{@api_key}#{separator}#{@connection_id}" }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# List the available unified tools.
|
|
32
|
+
def tools
|
|
33
|
+
@client.tools
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Call a unified tool with optional arguments.
|
|
37
|
+
def tool(name, args = {})
|
|
38
|
+
raise ArgumentError, 'Tool name is required to call a tool.' if name.nil? || name.to_s.empty?
|
|
39
|
+
|
|
40
|
+
@client.tool(name, args)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BundleUp
|
|
4
|
+
module Unify
|
|
5
|
+
# Client for ticketing Unify endpoints.
|
|
6
|
+
class Ticketing < Base
|
|
7
|
+
# Fetches tickets from the connected ticketing tool.
|
|
8
|
+
def tickets(params = {})
|
|
9
|
+
response = connection.get('ticketing/tickets') do |req|
|
|
10
|
+
req.params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
raise "Failed to fetch ticketing/tickets: #{response.status}" unless response.success?
|
|
14
|
+
|
|
15
|
+
response.body
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/bundleup/unify.rb
CHANGED
|
@@ -7,9 +7,43 @@ module BundleUp
|
|
|
7
7
|
attr_reader :api_key, :connection_id
|
|
8
8
|
|
|
9
9
|
def initialize(api_key, connection_id)
|
|
10
|
-
@
|
|
10
|
+
@ticketing = BundleUp::Unify::Ticketing.new(api_key, connection_id)
|
|
11
11
|
@chat = BundleUp::Unify::Chat.new(api_key, connection_id)
|
|
12
12
|
@git = BundleUp::Unify::Git.new(api_key, connection_id)
|
|
13
|
+
@crm = BundleUp::Unify::CRM.new(api_key, connection_id)
|
|
14
|
+
@drive = BundleUp::Unify::Drive.new(api_key, connection_id)
|
|
15
|
+
@api_key = api_key
|
|
16
|
+
@connection_id = connection_id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Access the Chat API for the connection.
|
|
20
|
+
def chat
|
|
21
|
+
@chat ||= BundleUp::Unify::Chat.new(api_key, connection_id)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Access the Git API for the connection.
|
|
25
|
+
def git
|
|
26
|
+
@git ||= BundleUp::Unify::Git.new(api_key, connection_id)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Access the CRM API for the connection.
|
|
30
|
+
def crm
|
|
31
|
+
@crm ||= BundleUp::Unify::CRM.new(api_key, connection_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Access the Ticketing API for the connection.
|
|
35
|
+
def ticketing
|
|
36
|
+
@ticketing ||= BundleUp::Unify::Ticketing.new(api_key, connection_id)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Access the Drive API for the connection.
|
|
40
|
+
def drive
|
|
41
|
+
@drive ||= BundleUp::Unify::Drive.new(api_key, connection_id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Access the Unified MCP server for the connection.
|
|
45
|
+
def mcp
|
|
46
|
+
@mcp ||= BundleUp::Unify::MCP.new(api_key, connection_id)
|
|
13
47
|
end
|
|
14
48
|
end
|
|
15
49
|
end
|
data/lib/bundleup/version.rb
CHANGED
data/lib/bundleup.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative 'bundleup/version'
|
|
|
6
6
|
require_relative 'bundleup/client'
|
|
7
7
|
require_relative 'bundleup/proxy'
|
|
8
8
|
require_relative 'bundleup/unify'
|
|
9
|
+
require_relative 'bundleup/mcp'
|
|
9
10
|
|
|
10
11
|
# Resources
|
|
11
12
|
require_relative 'bundleup/resources/base'
|
|
@@ -17,7 +18,10 @@ require_relative 'bundleup/resources/webhook'
|
|
|
17
18
|
require_relative 'bundleup/unify/base'
|
|
18
19
|
require_relative 'bundleup/unify/chat'
|
|
19
20
|
require_relative 'bundleup/unify/git'
|
|
20
|
-
require_relative 'bundleup/unify/
|
|
21
|
+
require_relative 'bundleup/unify/ticketing'
|
|
22
|
+
require_relative 'bundleup/unify/crm'
|
|
23
|
+
require_relative 'bundleup/unify/drive'
|
|
24
|
+
require_relative 'bundleup/unify/mcp'
|
|
21
25
|
|
|
22
26
|
# Main module for the BundleUp SDK.
|
|
23
27
|
module BundleUp
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class Base
|
|
4
|
+
BASE_URL: String
|
|
5
|
+
API_VERSION: String
|
|
6
|
+
|
|
7
|
+
attr_reader api_key: String
|
|
8
|
+
attr_reader connection_id: String
|
|
9
|
+
|
|
10
|
+
def initialize: (String api_key, String connection_id) -> void
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def connection: () -> Faraday::Connection
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class Chat < Base
|
|
4
|
+
type chat_user = { id: String, name: String }
|
|
5
|
+
type chat_channel = { id: String, name: String }
|
|
6
|
+
type metadata = { next: String? }
|
|
7
|
+
|
|
8
|
+
def users: (?Hash[Symbol, untyped] params) -> { data: Array[chat_user], metadata: metadata }
|
|
9
|
+
def channels: (?Hash[Symbol, untyped] params) -> { data: Array[chat_channel], metadata: metadata }
|
|
10
|
+
def message: (String channel_id, String text) -> { data: Hash[String, untyped] }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class CRM < Base
|
|
4
|
+
type crm_company = { id: String, name: String, website: String? }
|
|
5
|
+
type crm_contact = { id: String, name: String, email: String? }
|
|
6
|
+
type metadata = { next: String? }
|
|
7
|
+
|
|
8
|
+
def companies: (?Hash[Symbol, untyped] params) -> { data: Array[crm_company], metadata: metadata }
|
|
9
|
+
def contacts: (?Hash[Symbol, untyped] params) -> { data: Array[crm_contact], metadata: metadata }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class Drive < Base
|
|
4
|
+
type drive_file = {
|
|
5
|
+
id: String,
|
|
6
|
+
name: String,
|
|
7
|
+
mime_type: String?,
|
|
8
|
+
size: Integer?,
|
|
9
|
+
created_at: String?,
|
|
10
|
+
updated_at: String?,
|
|
11
|
+
url: String?,
|
|
12
|
+
is_folder: bool
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type metadata = { next: String? }
|
|
16
|
+
|
|
17
|
+
def files: (?Hash[Symbol, untyped] params) -> { data: Array[drive_file], metadata: metadata }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class Git < Base
|
|
4
|
+
type git_repo = {
|
|
5
|
+
id: Integer | String,
|
|
6
|
+
name: String,
|
|
7
|
+
full_name: String,
|
|
8
|
+
description: String?,
|
|
9
|
+
url: String,
|
|
10
|
+
created_at: String,
|
|
11
|
+
updated_at: String,
|
|
12
|
+
pushed_at: String
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type git_pull = {
|
|
16
|
+
id: Integer,
|
|
17
|
+
number: Integer,
|
|
18
|
+
title: String,
|
|
19
|
+
description: String?,
|
|
20
|
+
draft: bool,
|
|
21
|
+
state: String,
|
|
22
|
+
url: String,
|
|
23
|
+
user: String,
|
|
24
|
+
created_at: String,
|
|
25
|
+
updated_at: String,
|
|
26
|
+
merged_at: String?
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type git_tag = { name: String, commit_sha: String }
|
|
30
|
+
|
|
31
|
+
type git_release = {
|
|
32
|
+
id: Integer,
|
|
33
|
+
name: String,
|
|
34
|
+
tag_name: String,
|
|
35
|
+
description: String,
|
|
36
|
+
prerelease: bool,
|
|
37
|
+
url: String,
|
|
38
|
+
created_at: String,
|
|
39
|
+
released_at: String
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type git_branch = { name: String, commit_sha: String, protected: bool }
|
|
43
|
+
|
|
44
|
+
type metadata = { next: String? }
|
|
45
|
+
|
|
46
|
+
def repos: (?Hash[Symbol, untyped] params) -> { data: Array[git_repo], metadata: metadata }
|
|
47
|
+
def pulls: (String repo_name, ?Hash[Symbol, untyped] params) -> { data: Array[git_pull], metadata: metadata }
|
|
48
|
+
def tags: (String repo_name, ?Hash[Symbol, untyped] params) -> { data: Array[git_tag], metadata: metadata }
|
|
49
|
+
def releases: (String repo_name, ?Hash[Symbol, untyped] params) -> { data: Array[git_release], metadata: metadata }
|
|
50
|
+
def branches: (String repo_name, ?Hash[Symbol, untyped] params) -> { data: Array[git_branch], metadata: metadata }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class Ticketing < Base
|
|
4
|
+
type ticketing_ticket = {
|
|
5
|
+
id: String,
|
|
6
|
+
title: String,
|
|
7
|
+
status: String,
|
|
8
|
+
url: String,
|
|
9
|
+
description: String?,
|
|
10
|
+
created_at: String,
|
|
11
|
+
updated_at: String
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type metadata = { next: String? }
|
|
15
|
+
|
|
16
|
+
def issues: (?Hash[Symbol, untyped] params) -> { data: Array[ticketing_ticket], metadata: metadata }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module BundleUp
|
|
2
|
+
module Unify
|
|
3
|
+
class Client
|
|
4
|
+
attr_reader api_key: String
|
|
5
|
+
attr_reader connection_id: String
|
|
6
|
+
|
|
7
|
+
def initialize: (String api_key, String connection_id) -> void
|
|
8
|
+
|
|
9
|
+
def chat: () -> Chat
|
|
10
|
+
def git: () -> Git
|
|
11
|
+
def pm: () -> PM
|
|
12
|
+
def crm: () -> CRM
|
|
13
|
+
def drive: () -> Drive
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
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
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- BundleUp
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rbs
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: rspec
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,6 +80,20 @@ dependencies:
|
|
|
66
80
|
- - "~>"
|
|
67
81
|
- !ruby/object:Gem::Version
|
|
68
82
|
version: '1.50'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rubocop-capybara
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "<"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '2.23'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "<"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '2.23'
|
|
69
97
|
- !ruby/object:Gem::Dependency
|
|
70
98
|
name: rubocop-rake
|
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -140,6 +168,7 @@ files:
|
|
|
140
168
|
- README.md
|
|
141
169
|
- lib/bundleup.rb
|
|
142
170
|
- lib/bundleup/client.rb
|
|
171
|
+
- lib/bundleup/mcp.rb
|
|
143
172
|
- lib/bundleup/proxy.rb
|
|
144
173
|
- lib/bundleup/resources/base.rb
|
|
145
174
|
- lib/bundleup/resources/connection.rb
|
|
@@ -148,9 +177,19 @@ files:
|
|
|
148
177
|
- lib/bundleup/unify.rb
|
|
149
178
|
- lib/bundleup/unify/base.rb
|
|
150
179
|
- lib/bundleup/unify/chat.rb
|
|
180
|
+
- lib/bundleup/unify/crm.rb
|
|
181
|
+
- lib/bundleup/unify/drive.rb
|
|
151
182
|
- lib/bundleup/unify/git.rb
|
|
152
|
-
- lib/bundleup/unify/
|
|
183
|
+
- lib/bundleup/unify/mcp.rb
|
|
184
|
+
- lib/bundleup/unify/ticketing.rb
|
|
153
185
|
- lib/bundleup/version.rb
|
|
186
|
+
- sig/bundleup/unify.rbs
|
|
187
|
+
- sig/bundleup/unify/base.rbs
|
|
188
|
+
- sig/bundleup/unify/chat.rbs
|
|
189
|
+
- sig/bundleup/unify/crm.rbs
|
|
190
|
+
- sig/bundleup/unify/drive.rbs
|
|
191
|
+
- sig/bundleup/unify/git.rbs
|
|
192
|
+
- sig/bundleup/unify/ticketing.rbs
|
|
154
193
|
homepage: https://github.com/bundleup/bundleup-sdk-ruby
|
|
155
194
|
licenses:
|
|
156
195
|
- MIT
|
data/lib/bundleup/unify/pm.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BundleUp
|
|
4
|
-
module Unify
|
|
5
|
-
# Client for project management Unify endpoints.
|
|
6
|
-
class PM < Base
|
|
7
|
-
# Fetches issues from the connected project management tool.
|
|
8
|
-
def issues(params = {})
|
|
9
|
-
response = connection.get('pm/issues') do |req|
|
|
10
|
-
req.params = params
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
raise "Failed to fetch pm/issues: #{response.status}" unless response.success?
|
|
14
|
-
|
|
15
|
-
response.body
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|