anvil-ruby 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.
data/test_signature.rb ADDED
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test e-signature functionality with Anvil API
5
+
6
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
7
+ require 'anvil'
8
+ require 'anvil/env_loader'
9
+
10
+ # Load .env file
11
+ Anvil::EnvLoader.load(File.expand_path('.env', __dir__))
12
+
13
+ # Configure Anvil
14
+ Anvil.configure do |config|
15
+ config.api_key = ENV.fetch('ANVIL_API_KEY', nil)
16
+ config.environment = :development
17
+ end
18
+
19
+ puts '=' * 50
20
+ puts 'Anvil E-Signature Test'
21
+ puts '=' * 50
22
+
23
+ # Option 1: Create a test signature packet with a generated PDF
24
+ def create_test_packet_with_generated_pdf
25
+ puts "\nšŸ“ Creating a test signature packet..."
26
+
27
+ # First, generate a simple agreement PDF
28
+ puts 'šŸ“„ Generating agreement PDF...'
29
+
30
+ agreement_html = <<~HTML
31
+ <!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <title>Test Agreement</title>
35
+ </head>
36
+ <body style="font-family: Arial, sans-serif; padding: 40px;">
37
+ <h1 style="color: #333;">Service Agreement</h1>
38
+
39
+ <p><strong>Date:</strong> #{Date.today.strftime('%B %d, %Y')}</p>
40
+
41
+ <h2>Terms and Conditions</h2>
42
+ <p>This is a test agreement for demonstration purposes.</p>
43
+
44
+ <h3>1. Services</h3>
45
+ <p>The service provider agrees to provide software development services.</p>
46
+
47
+ <h3>2. Payment</h3>
48
+ <p>Payment terms will be NET 30.</p>
49
+
50
+ <h3>3. Confidentiality</h3>
51
+ <p>Both parties agree to maintain confidentiality.</p>
52
+
53
+ <div style="margin-top: 100px;">
54
+ <p><strong>Signature Fields:</strong></p>
55
+
56
+ <div style="margin-top: 50px;">
57
+ <p>_______________________________<br/>
58
+ Client Signature<br/>
59
+ Date: ________________</p>
60
+ </div>
61
+
62
+ <div style="margin-top: 50px;">
63
+ <p>_______________________________<br/>
64
+ Provider Signature<br/>
65
+ Date: ________________</p>
66
+ </div>
67
+ </div>
68
+ </body>
69
+ </html>
70
+ HTML
71
+
72
+ css = <<~CSS
73
+ body {
74
+ line-height: 1.6;
75
+ color: #333;
76
+ }
77
+ h1 {
78
+ border-bottom: 2px solid #007bff;
79
+ padding-bottom: 10px;
80
+ }
81
+ h2, h3 {
82
+ color: #555;
83
+ }
84
+ p {
85
+ margin: 10px 0;
86
+ }
87
+ CSS
88
+
89
+ # Generate the PDF
90
+ pdf = Anvil::PDF.generate_from_html(
91
+ html: agreement_html,
92
+ css: css,
93
+ title: 'Service Agreement'
94
+ )
95
+
96
+ # Save it temporarily
97
+ pdf_filename = "agreement_#{Time.now.to_i}.pdf"
98
+ pdf.save_as(pdf_filename)
99
+ puts "āœ… Agreement PDF saved as: #{pdf_filename}"
100
+
101
+ # NOTE: In a real scenario, you would:
102
+ # 1. Upload this PDF to Anvil to get a template ID
103
+ # 2. Or use an existing template ID from your Anvil account
104
+
105
+ puts "\nāš ļø Note: To create an actual signature packet, you need:"
106
+ puts ' 1. A PDF template ID from your Anvil account'
107
+ puts ' 2. Or upload the generated PDF to Anvil first'
108
+
109
+ pdf_filename
110
+ end
111
+
112
+ # Option 2: Create a signature packet with a template ID (if you have one)
113
+ def create_signature_packet_with_template(template_id = nil)
114
+ if template_id.nil?
115
+ puts "\nā— To test signatures with a template, you need a PDF template ID"
116
+ puts ' 1. Log into Anvil: https://app.useanvil.com'
117
+ puts ' 2. Upload a PDF template'
118
+ puts ' 3. Get the template ID'
119
+ puts ' 4. Pass it to this function'
120
+ return nil
121
+ end
122
+
123
+ puts "\nšŸš€ Creating signature packet with template: #{template_id}"
124
+
125
+ begin
126
+ packet = Anvil::Signature.create(
127
+ name: "Test Agreement - #{Date.today}",
128
+
129
+ # Define signers
130
+ signers: [
131
+ {
132
+ name: 'John Doe',
133
+ email: 'john.test@example.com',
134
+ role: 'client',
135
+ signer_type: 'email'
136
+ },
137
+ {
138
+ name: 'Jane Smith',
139
+ email: 'jane.test@example.com',
140
+ role: 'provider',
141
+ signer_type: 'email'
142
+ }
143
+ ],
144
+
145
+ # Reference the PDF template
146
+ files: [
147
+ {
148
+ type: :pdf,
149
+ id: template_id
150
+ }
151
+ ],
152
+
153
+ # Start as draft (signers won't be notified yet)
154
+ is_draft: true,
155
+
156
+ # Custom email settings
157
+ email_subject: 'Please sign the test agreement',
158
+ email_body: 'This is a test signature request from the Anvil Ruby gem.'
159
+
160
+ # Webhook for status updates (optional)
161
+ # webhook_url: 'https://your-app.com/webhooks/anvil'
162
+ )
163
+
164
+ puts 'āœ… Signature packet created successfully!'
165
+ puts "\nšŸ“‹ Packet Details:"
166
+ puts " ID: #{packet.eid}"
167
+ puts " Name: #{packet.name}"
168
+ puts " Status: #{packet.status}"
169
+ puts " Signers: #{packet.signers.count}"
170
+
171
+ # Get signing URLs for each signer
172
+ puts "\nšŸ”— Signing URLs:"
173
+ packet.signers.each do |signer|
174
+ url = signer.signing_url
175
+ puts "\n šŸ‘¤ #{signer.name} (#{signer.email})"
176
+ puts " Status: #{signer.status}"
177
+ puts " URL: #{url}"
178
+ puts ' -> Send this URL to the signer to collect their signature'
179
+ end
180
+
181
+ packet
182
+ rescue Anvil::Error => e
183
+ puts "āŒ Error creating signature packet: #{e.message}"
184
+ nil
185
+ end
186
+ end
187
+
188
+ # Option 3: Simple test with mock data (for demonstration)
189
+ def demonstrate_signature_api
190
+ puts "\nšŸ“š E-Signature API Demonstration"
191
+ puts '=' * 40
192
+
193
+ puts "\nThe Anvil::Signature class provides these methods:"
194
+ puts "\n1ļøāƒ£ Create a signature packet:"
195
+ puts <<~RUBY
196
+ packet = Anvil::Signature.create(
197
+ name: 'Employment Agreement',
198
+ signers: [
199
+ { name: 'John Doe', email: 'john@example.com', role: 'employee' },
200
+ { name: 'Jane HR', email: 'hr@company.com', role: 'hr_manager' }
201
+ ],
202
+ files: [{ type: :pdf, id: 'your_template_id' }],
203
+ is_draft: true # Start as draft
204
+ )
205
+ RUBY
206
+
207
+ puts "\n2ļøāƒ£ Get signing URLs:"
208
+ puts <<~RUBY
209
+ packet.signers.each do |signer|
210
+ puts "\#{signer.name}: \#{signer.signing_url}"
211
+ end
212
+ RUBY
213
+
214
+ puts "\n3ļøāƒ£ Check packet status:"
215
+ puts <<~RUBY
216
+ packet.reload!
217
+ if packet.complete?
218
+ puts "All signatures collected!"
219
+ elsif packet.in_progress?
220
+ puts "Still collecting signatures..."
221
+ end
222
+ RUBY
223
+
224
+ puts "\n4ļøāƒ£ Find existing packet:"
225
+ puts <<~RUBY
226
+ packet = Anvil::Signature.find('packet_eid_here')
227
+ puts "Status: \#{packet.status}"
228
+ RUBY
229
+
230
+ puts "\n5ļøāƒ£ List all packets:"
231
+ puts <<~RUBY
232
+ packets = Anvil::Signature.list(limit: 10, status: 'sent')
233
+ packets.each do |p|
234
+ puts "\#{p.name} - \#{p.status}"
235
+ end
236
+ RUBY
237
+ end
238
+
239
+ # Main execution
240
+ begin
241
+ # Show API demonstration
242
+ demonstrate_signature_api
243
+
244
+ # Generate a test PDF
245
+ create_test_packet_with_generated_pdf
246
+
247
+ # Try to create an actual signature packet
248
+ # Replace 'your_template_id' with an actual template ID from your Anvil account
249
+ template_id = ENV['ANVIL_TEMPLATE_ID'] || 'your_template_id_here'
250
+
251
+ if template_id == 'your_template_id_here'
252
+ puts "\nšŸ’” To test with real signatures:"
253
+ puts '1. Log into Anvil: https://app.useanvil.com'
254
+ puts '2. Create or upload a PDF template'
255
+ puts '3. Get the template ID from the template settings'
256
+ puts '4. Either:'
257
+ puts ' - Set ANVIL_TEMPLATE_ID in your .env file'
258
+ puts " - Or replace 'your_template_id_here' in this script"
259
+ puts '5. Run this script again'
260
+ else
261
+ packet = create_signature_packet_with_template(template_id)
262
+
263
+ if packet
264
+ puts "\nšŸŽÆ Next Steps:"
265
+ puts '1. Send the signing URLs to the signers (or test them yourself)'
266
+ puts '2. Complete the signatures'
267
+ puts '3. Check the packet status with: packet.reload!'
268
+ puts '4. Download signed documents when complete'
269
+ end
270
+ end
271
+
272
+ puts "\nāœ… E-signature test complete!"
273
+ rescue Anvil::AuthenticationError => e
274
+ puts "āŒ Authentication failed: #{e.message}"
275
+ puts 'Please check your API key in .env'
276
+ rescue Anvil::Error => e
277
+ puts "āŒ Anvil error: #{e.message}"
278
+ rescue StandardError => e
279
+ puts "āŒ Unexpected error: #{e.message}"
280
+ puts e.backtrace.first(5)
281
+ end
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Test Anvil E-Signatures with a Template
5
+ # First, add your template ID to .env: ANVIL_TEMPLATE_ID=your_cast_eid
6
+
7
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
8
+ require 'anvil'
9
+ require 'anvil/env_loader'
10
+
11
+ # Load .env file
12
+ Anvil::EnvLoader.load(File.expand_path('.env', __dir__))
13
+
14
+ # Configure Anvil
15
+ Anvil.configure do |config|
16
+ config.api_key = ENV.fetch('ANVIL_API_KEY', nil)
17
+ config.environment = :development
18
+ end
19
+
20
+ puts '=' * 50
21
+ puts 'šŸ–Šļø Anvil E-Signature with Template'
22
+ puts '=' * 50
23
+
24
+ template_id = ENV.fetch('ANVIL_TEMPLATE_ID', nil)
25
+
26
+ if template_id.nil? || template_id.empty?
27
+ puts "\nāš ļø No template ID found!"
28
+ puts "\nTo test e-signatures:"
29
+ puts '1. Log into Anvil: https://app.useanvil.com'
30
+ puts "2. Go to 'PDF Templates' and upload a PDF"
31
+ puts '3. Click on the template to view details'
32
+ puts "4. Copy the 'Cast EID' (looks like: XnuTZKVNA1Mljsu999od)"
33
+ puts '5. Add to your .env file:'
34
+ puts ' ANVIL_TEMPLATE_ID=your_cast_eid_here'
35
+ puts '6. Run this script again'
36
+ exit
37
+ end
38
+
39
+ puts "\nUsing template: #{template_id}"
40
+
41
+ begin
42
+ # Use our Ruby gem's Signature class
43
+ packet = Anvil::Signature.create(
44
+ name: "Test Agreement - #{DateTime.now.strftime('%Y-%m-%d %H:%M')}",
45
+
46
+ signers: [
47
+ {
48
+ name: 'John Doe',
49
+ email: 'john.test@example.com',
50
+ role: 'signer1',
51
+ signer_type: 'email'
52
+ },
53
+ {
54
+ name: 'Jane Smith',
55
+ email: 'jane.test@example.com',
56
+ role: 'signer2',
57
+ signer_type: 'email'
58
+ }
59
+ ],
60
+
61
+ # Use your template
62
+ files: [
63
+ {
64
+ type: :pdf,
65
+ id: template_id
66
+ }
67
+ ],
68
+
69
+ # Start as draft so no emails are sent yet
70
+ is_draft: true,
71
+
72
+ # Custom email text
73
+ email_subject: 'Test Signature Request from Ruby Gem',
74
+ email_body: 'Please sign this test document.'
75
+ )
76
+
77
+ puts "\nāœ… Signature packet created successfully!"
78
+ puts "\nšŸ“‹ Packet Details:"
79
+ puts " ID: #{packet.eid}"
80
+ puts " Name: #{packet.name}"
81
+ puts " Status: #{packet.status}"
82
+
83
+ # Get signing URLs
84
+ puts "\nšŸ”— Signing URLs:"
85
+ packet.signers.each do |signer|
86
+ puts "\n šŸ‘¤ #{signer.name} (#{signer.email})"
87
+ puts " Status: #{signer.status}"
88
+
89
+ # Generate signing URL
90
+ url = signer.signing_url
91
+ puts " URL: #{url}"
92
+ end
93
+
94
+ puts "\nšŸŽÆ Next Steps:"
95
+ puts '1. Copy a signing URL above'
96
+ puts '2. Open it in a browser to test the signing experience'
97
+ puts '3. Or change is_draft to false to send real emails'
98
+ puts '4. Check packet status: packet.reload!'
99
+ rescue Anvil::ValidationError => e
100
+ puts "\nāŒ Validation error: #{e.message}"
101
+ puts 'Make sure your template ID is correct'
102
+ rescue Anvil::AuthenticationError => e
103
+ puts "\nāŒ Authentication error: #{e.message}"
104
+ rescue Anvil::Error => e
105
+ puts "\nāŒ Error: #{e.message}"
106
+ puts "\nšŸ’” If the template ID doesn't work:"
107
+ puts " - Make sure it's the 'Cast EID' not the template name"
108
+ puts ' - Check that the template has signature fields configured'
109
+ rescue StandardError => e
110
+ puts "\nāŒ Unexpected error: #{e.message}"
111
+ puts e.backtrace.first(5)
112
+ end
metadata ADDED
@@ -0,0 +1,247 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: anvil-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Marazzo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-03-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.17'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.17'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.50'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.50'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.20'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.20'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.22'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.22'
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '6.1'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '6.1'
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.18'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.18'
139
+ - !ruby/object:Gem::Dependency
140
+ name: yard
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.9'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.9'
153
+ - !ruby/object:Gem::Dependency
154
+ name: multipart-post
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.3'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.3'
167
+ description: |
168
+ Official Ruby client for the Anvil API. Anvil is a suite of tools for
169
+ managing document workflows including PDF filling, PDF generation from HTML/Markdown,
170
+ e-signatures, and webhooks. Built with zero runtime dependencies and designed
171
+ to be Rails-friendly while remaining framework agnostic.
172
+ email:
173
+ - nick.marazzo@kodehealth.com
174
+ executables: []
175
+ extensions: []
176
+ extra_rdoc_files: []
177
+ files:
178
+ - AGENTS.md
179
+ - API_COVERAGE.md
180
+ - CHANGELOG.md
181
+ - CLAUDE_README.md
182
+ - GITHUB_ACTIONS_QUICKREF.md
183
+ - Gemfile.lock
184
+ - Gemfile.minimal
185
+ - LICENSE
186
+ - PROJECT_CONTEXT.md
187
+ - README.md
188
+ - anvil-ruby.gemspec
189
+ - bin/console
190
+ - bin/setup
191
+ - create_signature_direct.rb
192
+ - create_signature_packet.rb
193
+ - debug_env.rb
194
+ - examples/create_signature.rb
195
+ - examples/fill_pdf.rb
196
+ - examples/generate_pdf.rb
197
+ - examples/verify_webhook.rb
198
+ - lib/anvil.rb
199
+ - lib/anvil/client.rb
200
+ - lib/anvil/configuration.rb
201
+ - lib/anvil/env_loader.rb
202
+ - lib/anvil/errors.rb
203
+ - lib/anvil/rate_limiter.rb
204
+ - lib/anvil/resources/base.rb
205
+ - lib/anvil/resources/pdf.rb
206
+ - lib/anvil/resources/signature.rb
207
+ - lib/anvil/resources/webform.rb
208
+ - lib/anvil/resources/webhook.rb
209
+ - lib/anvil/resources/workflow.rb
210
+ - lib/anvil/response.rb
211
+ - lib/anvil/version.rb
212
+ - quickstart_signature.rb
213
+ - test_api_connection.rb
214
+ - test_etch_signature.rb
215
+ - test_gem.rb
216
+ - test_signature.rb
217
+ - test_signature_with_template.rb
218
+ homepage: https://github.com/nickMarz/Ruby-Anvil
219
+ licenses:
220
+ - MIT
221
+ metadata:
222
+ homepage_uri: https://github.com/nickMarz/Ruby-Anvil
223
+ source_code_uri: https://github.com/nickMarz/Ruby-Anvil
224
+ changelog_uri: https://github.com/nickMarz/Ruby-Anvil/blob/main/CHANGELOG.md
225
+ bug_tracker_uri: https://github.com/nickMarz/Ruby-Anvil/issues
226
+ documentation_uri: https://www.rubydoc.info/gems/anvil-ruby
227
+ rubygems_mfa_required: 'true'
228
+ post_install_message:
229
+ rdoc_options: []
230
+ require_paths:
231
+ - lib
232
+ required_ruby_version: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: 2.5.0
237
+ required_rubygems_version: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: '0'
242
+ requirements: []
243
+ rubygems_version: 3.4.19
244
+ signing_key:
245
+ specification_version: 4
246
+ summary: Ruby client for the Anvil API - document automation and e-signatures
247
+ test_files: []