myrr-rb 0.3.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.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Myrr
4
+ # Read-only value object representing a wallet transaction.
5
+ class Transaction
6
+ attr_reader :id, :amount, :currency, :status, :source, :merchant_name,
7
+ :merchant_category, :decline_reason, :created_at
8
+
9
+ # @param attrs [Hash] Transaction attributes from the API
10
+ def initialize(attrs)
11
+ @id = attrs["id"]
12
+ @amount = attrs["amount"]
13
+ @currency = attrs["currency"] || "usd"
14
+ @status = attrs["status"]
15
+ @source = attrs["source"]
16
+ @merchant_name = attrs["merchant_name"]
17
+ @merchant_category = attrs["merchant_category"]
18
+ @decline_reason = attrs["decline_reason"]
19
+ @created_at = attrs["created_at"]
20
+ end
21
+
22
+ # @return [String] Human-readable amount
23
+ def amount_dollars
24
+ "$" + format("%.2f", @amount.to_f / 100)
25
+ end
26
+
27
+ # @return [Boolean] Whether the transaction was authorized
28
+ def authorized?
29
+ @status == "authorized"
30
+ end
31
+
32
+ # @return [Boolean] Whether the transaction was declined
33
+ def declined?
34
+ @status == "declined"
35
+ end
36
+
37
+ # @return [Boolean] Whether this is a funding transaction
38
+ def funding?
39
+ @status == "funding"
40
+ end
41
+
42
+ # @return [Boolean] Whether this is a Myrr Pay top-up
43
+ def myrr_pay_topup?
44
+ @source == "myrr_pay_topup"
45
+ end
46
+
47
+ # @return [Boolean] Whether this is a wallet funding
48
+ def wallet_funding?
49
+ @source == "wallet_funding"
50
+ end
51
+
52
+ # @return [Boolean] Whether this is a regular merchant purchase
53
+ def merchant_purchase?
54
+ @source.nil? || @source == "merchant_purchase"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Myrr
2
+ VERSION = "0.3.3"
3
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "client"
5
+
6
+ module Myrr
7
+ # Wallet client for the Myrr protocol server.
8
+ #
9
+ # @example
10
+ # wallet = Myrr::Wallet.create(agent_did: "did:myrr:abc123")
11
+ # wallet = Myrr::Wallet.find("wallet-uuid")
12
+ # wallet = Myrr::Wallet.find_by_agent("did:myrr:abc123")
13
+ # wallet.fund(amount_cents: 50_00)
14
+ # wallet.cards # => Array of Myrr::Card
15
+ # wallet.transactions(limit: 10)
16
+ # wallet.topup(seller_did: "did:myrr:seller", seller_name: "DataPulse", amount_cents: 10_00, payment_intent_id: "pi_xxx")
17
+ class Wallet
18
+ attr_reader :id, :agent_did, :balance, :currency, :status, :stripe_customer_id,
19
+ :metadata, :created_at, :updated_at
20
+
21
+ # @param attrs [Hash] Wallet attributes from the API
22
+ def initialize(attrs)
23
+ @id = attrs["id"]
24
+ @agent_did = attrs["agent_did"]
25
+ @balance = attrs["balance"]
26
+ @currency = attrs["currency"] || "usd"
27
+ @status = attrs["status"]
28
+ @stripe_customer_id = attrs["stripe_customer_id"]
29
+ @metadata = attrs["metadata"]
30
+ @created_at = attrs["created_at"]
31
+ @updated_at = attrs["updated_at"]
32
+ end
33
+
34
+ # Create a new wallet for the given agent.
35
+ #
36
+ # @param agent_did [String] The did:myrr: identifier
37
+ # @param metadata [Hash] Optional metadata
38
+ # @param client [Myrr::Client, nil] Optional custom client
39
+ # @return [Myrr::Wallet]
40
+ def self.create(agent_did:, metadata: {}, client: nil)
41
+ client ||= Myrr.client
42
+ body = { agent_did: agent_did }
43
+ body[:metadata] = metadata if metadata.any?
44
+ resp = client.send(:post, "/v1/wallets", body)
45
+ new(resp)
46
+ end
47
+
48
+ # Find a wallet by its UUID.
49
+ #
50
+ # @param id [String] Wallet UUID
51
+ # @param client [Myrr::Client, nil] Optional custom client
52
+ # @return [Myrr::Wallet, nil]
53
+ def self.find(id, client: nil)
54
+ client ||= Myrr.client
55
+ resp = client.send(:get, "/v1/wallets/#{id}")
56
+ new(resp)
57
+ rescue Myrr::Error => e
58
+ raise unless e.message.include?("not_found")
59
+ nil
60
+ end
61
+
62
+ # Find a wallet by its agent did:myrr: identifier.
63
+ #
64
+ # @param agent_did [String] The did:myrr: identifier
65
+ # @param client [Myrr::Client, nil] Optional custom client
66
+ # @return [Myrr::Wallet, nil]
67
+ def self.find_by_agent(agent_did, client: nil)
68
+ client ||= Myrr.client
69
+ resp = client.send(:get, "/v1/wallets?agent_did=#{agent_did}")
70
+ new(resp)
71
+ rescue Myrr::Error => e
72
+ raise unless e.message.include?("not_found")
73
+ nil
74
+ end
75
+
76
+ # Pre-approve a Myrr Pay top-up before confirming the PaymentIntent.
77
+ #
78
+ # @param seller_did [String] Seller's did:myrr: identifier
79
+ # @param seller_name [String] Seller's display name
80
+ # @param amount_cents [Integer] Top-up amount in cents
81
+ # @param payment_intent_id [String] Stripe PaymentIntent ID
82
+ # @param metadata [Hash] Optional metadata
83
+ # @return [Hash] { "status" => "approved"/"declined", "reason" => String? }
84
+ def topup(seller_did:, seller_name:, amount_cents:, payment_intent_id: nil, metadata: {})
85
+ body = {
86
+ seller_did: seller_did,
87
+ seller_name: seller_name,
88
+ amount_cents: amount_cents,
89
+ payment_intent_id: payment_intent_id
90
+ }
91
+ body[:metadata] = metadata if metadata.any?
92
+ self.class.client.send(:post, "/v1/wallets/#{id}/topups", body)
93
+ end
94
+
95
+ # Create a Stripe Checkout session to fund this wallet.
96
+ #
97
+ # @param amount_cents [Integer] Amount in cents (minimum 50)
98
+ # @return [String] Checkout URL
99
+ def fund(amount_cents:)
100
+ resp = self.class.client.send(:post, "/v1/wallets/#{id}/fund", { amount_cents: amount_cents })
101
+ resp["checkout_url"]
102
+ end
103
+
104
+ # List virtual cards for this wallet.
105
+ #
106
+ # @return [Array<Myrr::Card>]
107
+ def cards
108
+ resp = self.class.client.send(:get, "/v1/wallets/#{id}/cards")
109
+ (resp["cards"] || []).map { |c| Myrr::Card.new(c) }
110
+ end
111
+
112
+ # List transactions for this wallet.
113
+ #
114
+ # @param limit [Integer] Max results (default 20, max 100)
115
+ # @param before [String, nil] Cursor for pagination
116
+ # @return [Array<Myrr::Transaction>]
117
+ def transactions(limit: 20, before: nil)
118
+ query = "limit=#{limit}"
119
+ query += "&before=#{before}" if before
120
+ resp = self.class.client.send(:get, "/v1/wallets/#{id}/transactions?#{query}")
121
+ txns = (resp["transactions"] || []).map { |t| Myrr::Transaction.new(t) }
122
+ [txns, resp["has_more"], resp["next_cursor"]]
123
+ end
124
+
125
+ private
126
+
127
+ def self.client
128
+ Myrr.client
129
+ end
130
+ end
131
+ end
data/lib/myrr-rb.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Entry point for bundler's auto-require.
2
+ # Bundler calls `require "myrr-rb"` for `gem "myrr-rb"`.
3
+ require_relative "myrr"
data/lib/myrr.rb ADDED
@@ -0,0 +1,76 @@
1
+ require_relative "myrr/version"
2
+ require_relative "myrr/configuration"
3
+ require_relative "myrr/tokenizer"
4
+ require_relative "myrr/port/middleware"
5
+ require_relative "myrr/railtie" if defined?(Rails::Railtie)
6
+
7
+ # Myrr — Agent-friendly content for your Rails app.
8
+ #
9
+ # In its default (Port) mode, myrr-rb counts tokens on every text/markdown
10
+ # response and adds an +X-Token-Count+ header. No Go server, no database,
11
+ # no configuration required.
12
+ #
13
+ # @example Zero-config setup
14
+ # # Just add the gem, create .md.erb views, and you're done.
15
+ module Myrr
16
+ class Error < StandardError; end
17
+
18
+ class << self
19
+ # The current configuration object.
20
+ # @return [Myrr::Configuration]
21
+ attr_reader :config
22
+
23
+ # Configure Myrr.
24
+ #
25
+ # @yield [config] Yields the configuration object for modification.
26
+ # @yieldparam config [Myrr::Configuration]
27
+ def configure
28
+ @config ||= Configuration.new
29
+ yield(@config) if block_given?
30
+ @config
31
+ end
32
+
33
+ # Reset configuration to defaults.
34
+ def reset_config!
35
+ @config = Configuration.new
36
+ end
37
+
38
+ # Create a new protocol server client.
39
+ # Requires +protocol_server_url+ to be configured.
40
+ # (Identity-layer feature — opt-in, requires explicit require.)
41
+ #
42
+ # @return [Myrr::Client]
43
+ def client
44
+ Client.new(
45
+ server_url: config.protocol_server_url,
46
+ api_key: config.site_api_key,
47
+ open_timeout: config.open_timeout,
48
+ read_timeout: config.read_timeout
49
+ )
50
+ end
51
+
52
+ # Verify an agent identity. Returns agent info or nil.
53
+ # (Identity-layer feature — opt-in, requires explicit require.)
54
+ #
55
+ # Performs challenge-response verification via the configured protocol
56
+ # server (or identity_adapter). If +fail_open+ is true, returns nil
57
+ # on network errors instead of raising.
58
+ #
59
+ # @param did_key [String] The did:myrr: identifier
60
+ # @return [Hash, nil] Agent info with +did_key+, +status+, +owner+, or nil
61
+ def verify_agent(did_key)
62
+ if config.identity_adapter
63
+ config.identity_adapter.call(did_key)
64
+ else
65
+ client.verify(did_key)
66
+ end
67
+ rescue => e
68
+ if config.fail_open
69
+ warn "[myrr] Identity verification failed (fail_open=true): #{e.message}"
70
+ nil
71
+ else
72
+ raise
73
+ end
74
+ end
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: myrr-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.3
5
+ platform: ruby
6
+ authors:
7
+ - Kaka Ruto
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rack
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: tiktoken_ruby
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.0.16
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.0.16
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.20'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.20'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rack-test
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.1'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.1'
68
+ - !ruby/object:Gem::Dependency
69
+ name: webmock
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.19'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.19'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rake
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '13.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '13.0'
96
+ description: Count tokens on text/markdown responses, add an X-Token-Count header,
97
+ and serve .md.erb views alongside .html.erb. Zero config out of the box.
98
+ email:
99
+ - kr@kakaruto.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - CHANGELOG.md
105
+ - LICENSE
106
+ - README.md
107
+ - lib/generators/myrr/install/install_generator.rb
108
+ - lib/generators/myrr/install/templates/initializer.rb
109
+ - lib/myrr-rb.rb
110
+ - lib/myrr.rb
111
+ - lib/myrr/bridge.rb
112
+ - lib/myrr/budget.rb
113
+ - lib/myrr/card.rb
114
+ - lib/myrr/client.rb
115
+ - lib/myrr/configuration.rb
116
+ - lib/myrr/pay.rb
117
+ - lib/myrr/pay/client.rb
118
+ - lib/myrr/pay/middleware.rb
119
+ - lib/myrr/port/metadata.rb
120
+ - lib/myrr/port/middleware.rb
121
+ - lib/myrr/rack/middleware.rb
122
+ - lib/myrr/railtie.rb
123
+ - lib/myrr/tokenizer.rb
124
+ - lib/myrr/transaction.rb
125
+ - lib/myrr/version.rb
126
+ - lib/myrr/wallet.rb
127
+ homepage: https://myrrlabs.com
128
+ licenses:
129
+ - MIT
130
+ metadata:
131
+ homepage_uri: https://myrrlabs.com
132
+ source_code_uri: https://github.com/myrrlabs/myrr-rb
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '3.0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubygems_version: 4.0.3
148
+ specification_version: 4
149
+ summary: Agent-friendly content for your Rails app
150
+ test_files: []