administrate-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +493 -0
  4. data/lib/administrate/base_model.rb +108 -0
  5. data/lib/administrate/client.rb +27 -0
  6. data/lib/administrate/configuration.rb +22 -0
  7. data/lib/administrate/cursor_iterator.rb +41 -0
  8. data/lib/administrate/errors.rb +42 -0
  9. data/lib/administrate/models/account.rb +24 -0
  10. data/lib/administrate/models/api_token.rb +16 -0
  11. data/lib/administrate/models/client_model.rb +16 -0
  12. data/lib/administrate/models/execution.rb +12 -0
  13. data/lib/administrate/models/instance.rb +17 -0
  14. data/lib/administrate/models/llm_cost.rb +37 -0
  15. data/lib/administrate/models/llm_project.rb +10 -0
  16. data/lib/administrate/models/llm_provider.rb +16 -0
  17. data/lib/administrate/models/pagination_meta.rb +9 -0
  18. data/lib/administrate/models/sync_run.rb +23 -0
  19. data/lib/administrate/models/user.rb +14 -0
  20. data/lib/administrate/models/webhook.rb +11 -0
  21. data/lib/administrate/models/workflow.rb +18 -0
  22. data/lib/administrate/page.rb +25 -0
  23. data/lib/administrate/resources/account.rb +24 -0
  24. data/lib/administrate/resources/api_tokens.rb +37 -0
  25. data/lib/administrate/resources/base_resource.rb +37 -0
  26. data/lib/administrate/resources/clients.rb +49 -0
  27. data/lib/administrate/resources/executions.rb +28 -0
  28. data/lib/administrate/resources/instances.rb +61 -0
  29. data/lib/administrate/resources/llm_costs.rb +70 -0
  30. data/lib/administrate/resources/llm_projects.rb +20 -0
  31. data/lib/administrate/resources/llm_providers.rb +47 -0
  32. data/lib/administrate/resources/sync_runs.rb +30 -0
  33. data/lib/administrate/resources/users.rb +33 -0
  34. data/lib/administrate/resources/webhooks.rb +43 -0
  35. data/lib/administrate/resources/workflows.rb +29 -0
  36. data/lib/administrate/transport.rb +101 -0
  37. data/lib/administrate/version.rb +5 -0
  38. data/lib/administrate-sdk.rb +3 -0
  39. data/lib/administrate.rb +42 -0
  40. metadata +123 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bf5605805daacbe02a2d9c84fba5cc4c3e6fa7b3d690087e80664b959a0c7c3d
4
+ data.tar.gz: 2053517211d59122b09bddf04f4c2cbd37cc8076cebfd2a6adf2d5362400ca32
5
+ SHA512:
6
+ metadata.gz: eedd337177788041d4f65ef2a45ce183de4e7573e86057a0f323fc72c341825fb4205ad02516406a95d34ce4e8bd19a7fbf6f1315fea3eb7af25bf6582799522
7
+ data.tar.gz: dd2f7c9016e94de95a39e9d519e4ce97025eb18dd8368be029244e6973eb87872dbcc13ab891fee2b13af32e4bca496a0bdc8cc1d381038d381e57962552ea2d
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Administrate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,493 @@
1
+ # Administrate Ruby SDK
2
+
3
+ The official Ruby SDK for the [Administrate.dev](https://administrate.dev) REST API.
4
+
5
+ [Administrate.dev](https://administrate.dev) is AI Agency Management software. It is a monitoring and management platform for AI agencies running n8n and AI automation workflows across multiple clients. It provides a single dashboard to track every workflow, every client, every failure, and all LLM costs, so you can catch problems before clients do and prove the value of your automations.
6
+
7
+ **Key platform features:**
8
+
9
+ - **Multi-instance monitoring** -- See all n8n instances across every client in one place
10
+ - **Error tracking** -- Real-time failure detection with automatic error categorization
11
+ - **LLM cost tracking** -- Connect OpenAI, Anthropic, Azure, and OpenRouter accounts to attribute costs to specific clients
12
+ - **Workflow insights** -- Execution counts, success rates, and time-saved ROI reporting
13
+ - **Sync health** -- Know instantly when a data sync fails
14
+ - **Webhooks & API** -- Full programmatic access for custom integrations
15
+
16
+ ## Installation
17
+
18
+ Add to your Gemfile:
19
+
20
+ ```ruby
21
+ gem 'administrate-sdk'
22
+ ```
23
+
24
+ Then run:
25
+
26
+ ```bash
27
+ bundle install
28
+ ```
29
+
30
+ Or install directly:
31
+
32
+ ```bash
33
+ gem install administrate-sdk
34
+ ```
35
+
36
+ Requires Ruby 3.1+.
37
+
38
+ ## Quick start
39
+
40
+ ```ruby
41
+ require 'administrate-sdk'
42
+
43
+ client = Administrate.new(api_key: 'sk_live_...')
44
+
45
+ # Get account info
46
+ account = client.account.get
47
+ puts "#{account.name} (#{account.plan})"
48
+
49
+ # List all clients with auto-pagination
50
+ client.clients.list.each do |c|
51
+ puts "#{c.name} (#{c.code})"
52
+ end
53
+
54
+ # Check for failed executions across all instances
55
+ client.executions.list(errors_only: true).each do |execution|
56
+ puts "#{execution.workflow_name}: #{execution.error_category}"
57
+ end
58
+
59
+ # Get LLM cost summary
60
+ costs = client.llm_costs.summary
61
+ puts "Total: $#{'%.2f' % (costs.data.summary.total_cost_cents / 100.0)}"
62
+ ```
63
+
64
+ ## Configuration
65
+
66
+ ```ruby
67
+ require 'administrate-sdk'
68
+
69
+ client = Administrate.new(
70
+ api_key: 'sk_live_...', # Required. Must start with "sk_live_"
71
+ base_url: 'https://...', # Default: "https://administrate.dev"
72
+ timeout: 30, # Request timeout in seconds. Default: 30
73
+ max_retries: 3 # Retry attempts for failed requests. Default: 3
74
+ )
75
+ ```
76
+
77
+ The SDK uses [Faraday](https://lostisland.github.io/faraday/) under the hood. Retry and backoff are handled automatically via the `faraday-retry` middleware.
78
+
79
+ ## API reference
80
+
81
+ All API keys are created in **Settings > Developers** within Administrate.dev. Tokens have three permission levels: `read`, `write`, and `full`.
82
+
83
+ ### Account
84
+
85
+ ```ruby
86
+ # Get current token info and account summary
87
+ me = client.account.me
88
+ puts "#{me.token.name} (#{me.token.permission})"
89
+ puts "#{me.account.name} (#{me.account.plan})"
90
+
91
+ # Get full account details
92
+ account = client.account.get
93
+
94
+ # Update account settings
95
+ account = client.account.update(
96
+ name: 'My Agency',
97
+ billing_email: 'billing@example.com',
98
+ timezone: 'Australia/Brisbane'
99
+ )
100
+ ```
101
+
102
+ ### Clients
103
+
104
+ Clients represent the companies you manage automations for.
105
+
106
+ ```ruby
107
+ # List all clients (auto-paginates)
108
+ client.clients.list.each do |c|
109
+ puts "#{c.name} (#{c.code})"
110
+ end
111
+
112
+ # Get a client (includes 7-day metrics)
113
+ c = client.clients.get('com_abc123')
114
+ puts "#{c.metrics.success_rate}% success, #{c.metrics.time_saved_minutes} min saved"
115
+
116
+ # Create a client
117
+ c = client.clients.create(
118
+ name: 'Acme Corp',
119
+ code: 'acme',
120
+ contact_email: 'ops@acme.com',
121
+ timezone: 'America/New_York'
122
+ )
123
+
124
+ # Update a client
125
+ c = client.clients.update('com_abc123', notes: 'Enterprise tier')
126
+
127
+ # Delete a client (requires full permission)
128
+ client.clients.delete('com_abc123')
129
+ ```
130
+
131
+ ### Instances
132
+
133
+ Instances are n8n deployments connected to Administrate.
134
+
135
+ ```ruby
136
+ # List all instances
137
+ client.instances.list.each do |inst|
138
+ puts "#{inst.name} (#{inst.sync_status})"
139
+ end
140
+
141
+ # Filter by client or sync status
142
+ client.instances.list(client_id: 'com_abc123', sync_status: 'error').each do |inst|
143
+ puts "#{inst.name}: #{inst.last_sync_error}"
144
+ end
145
+
146
+ # Get an instance (includes 7-day metrics)
147
+ inst = client.instances.get('n8n_abc123')
148
+ puts "#{inst.metrics.executions_count} executions, #{inst.metrics.success_rate}% success"
149
+
150
+ # Connect a new n8n instance
151
+ inst = client.instances.create(
152
+ client_id: 'com_abc123',
153
+ name: 'Production n8n',
154
+ base_url: 'https://n8n.acme.com',
155
+ api_key: 'n8n_api_key_here'
156
+ )
157
+
158
+ # Trigger a sync
159
+ client.instances.sync('n8n_abc123', sync_type: 'all')
160
+
161
+ # Sync all instances at once
162
+ client.instances.sync_all(sync_type: 'workflows')
163
+
164
+ # Update an instance
165
+ inst = client.instances.update('n8n_abc123', name: 'Staging n8n')
166
+
167
+ # Delete an instance
168
+ client.instances.delete('n8n_abc123')
169
+ ```
170
+
171
+ ### Workflows
172
+
173
+ ```ruby
174
+ # List workflows with filters
175
+ client.workflows.list(client_id: 'com_abc123', active: true).each do |wf|
176
+ puts "#{wf.name} (active: #{wf.is_active})"
177
+ end
178
+
179
+ # Search by name
180
+ client.workflows.list(search: 'onboarding').each do |wf|
181
+ puts wf.name
182
+ end
183
+
184
+ # Get a workflow (includes 7-day metrics)
185
+ wf = client.workflows.get('wfl_abc123')
186
+ puts "#{wf.metrics.success_rate}% success, #{wf.metrics.time_saved_minutes} min saved"
187
+
188
+ # Set time-saved estimates (for ROI reporting)
189
+ wf = client.workflows.update(
190
+ 'wfl_abc123',
191
+ minutes_saved_per_success: 15,
192
+ minutes_saved_per_failure: 5
193
+ )
194
+ ```
195
+
196
+ ### Executions
197
+
198
+ Executions are read-only records of workflow runs synced from n8n.
199
+
200
+ ```ruby
201
+ # List executions with filters
202
+ client.executions.list(
203
+ client_id: 'com_abc123',
204
+ status: 'failed',
205
+ start_date: '2025-01-01',
206
+ end_date: '2025-01-31'
207
+ ).each do |ex|
208
+ puts "#{ex.workflow_name} - #{ex.status} (#{ex.duration_ms}ms)"
209
+ end
210
+
211
+ # Get only errors
212
+ client.executions.list(errors_only: true).each do |ex|
213
+ puts "#{ex.error_category}: #{ex.workflow_name}"
214
+ end
215
+
216
+ # Get execution details (includes error message and payload)
217
+ ex = client.executions.get('exe_abc123')
218
+ puts ex.error_message
219
+ puts ex.error_payload
220
+ ```
221
+
222
+ ### Sync runs
223
+
224
+ ```ruby
225
+ # List sync run history
226
+ client.sync_runs.list(instance_id: 'n8n_abc123', status: 'failed').each do |run|
227
+ puts "#{run.sync_type} - #{run.status} (#{run.duration_seconds}s)"
228
+ end
229
+
230
+ # Get a specific sync run
231
+ run = client.sync_runs.get('syn_abc123')
232
+
233
+ # Get sync health across all instances
234
+ client.sync_runs.health.each do |entry|
235
+ puts "#{entry.instance_name} (#{entry.sync_status})"
236
+ puts " Workflows last synced: #{entry.workflows.last_synced_at}"
237
+ puts " Executions last synced: #{entry.executions.last_synced_at}"
238
+ end
239
+ ```
240
+
241
+ ### Users
242
+
243
+ ```ruby
244
+ # List team members
245
+ client.users.list.each do |user|
246
+ puts "#{user.name} <#{user.email}> (#{user.role})"
247
+ end
248
+
249
+ # Get a user
250
+ user = client.users.get('usr_abc123')
251
+
252
+ # Invite a new team member
253
+ invitation = client.users.invite(email: 'new@example.com', role: 'member')
254
+ puts invitation.expires_at
255
+
256
+ # Change a user's role
257
+ user = client.users.update('usr_abc123', role: 'admin')
258
+
259
+ # Remove a user
260
+ client.users.delete('usr_abc123')
261
+ ```
262
+
263
+ ### Webhooks
264
+
265
+ ```ruby
266
+ # List webhooks
267
+ client.webhooks.list.each do |wh|
268
+ puts "#{wh.url} - #{wh.events.join(', ')} (enabled: #{wh.enabled})"
269
+ end
270
+
271
+ # Create a webhook
272
+ wh = client.webhooks.create(
273
+ url: 'https://example.com/hook',
274
+ events: ['execution.failed', 'sync.failed'],
275
+ description: 'Slack failure alerts'
276
+ )
277
+ puts wh.secret # Save this -- used to verify webhook signatures
278
+
279
+ # Update a webhook
280
+ wh = client.webhooks.update('whk_abc123', enabled: false)
281
+
282
+ # Regenerate the signing secret (old secret becomes invalid immediately)
283
+ wh = client.webhooks.regenerate_secret('whk_abc123')
284
+ puts wh.secret
285
+
286
+ # Delete a webhook
287
+ client.webhooks.delete('whk_abc123')
288
+ ```
289
+
290
+ ### API tokens
291
+
292
+ ```ruby
293
+ # List all tokens
294
+ client.api_tokens.list.each do |token|
295
+ puts "#{token.name} (#{token.permission}) - #{token.token_hint}"
296
+ end
297
+
298
+ # Create a token (the plain token is only returned once)
299
+ token = client.api_tokens.create(
300
+ name: 'CI/CD Pipeline',
301
+ permission: 'read',
302
+ ip_allowlist: ['10.0.0.0/8'],
303
+ expires_in: '90_days'
304
+ )
305
+ puts token.token # sk_live_... -- save this immediately
306
+
307
+ # Update a token
308
+ token = client.api_tokens.update('tok_abc123', name: 'Updated Name')
309
+
310
+ # Revoke a token
311
+ client.api_tokens.delete('tok_abc123')
312
+ ```
313
+
314
+ ### LLM providers
315
+
316
+ Connect your AI provider accounts to track costs.
317
+
318
+ ```ruby
319
+ # List providers
320
+ client.llm_providers.list.each do |provider|
321
+ puts "#{provider.name} (#{provider.provider_type}) - #{provider.sync_status}"
322
+ end
323
+
324
+ # Get a provider (includes 7-day metrics)
325
+ provider = client.llm_providers.get('llm_abc123')
326
+ puts "#{provider.metrics.total_cost_cents}c, #{provider.metrics.total_tokens} tokens"
327
+
328
+ # Connect a new provider
329
+ provider = client.llm_providers.create(
330
+ name: 'OpenAI Production',
331
+ provider_type: 'openai', # openai, anthropic, openrouter, or azure
332
+ api_key: 'sk-...',
333
+ organization_id: 'org-...'
334
+ )
335
+
336
+ # Trigger a cost sync
337
+ client.llm_providers.sync('llm_abc123')
338
+
339
+ # Update a provider
340
+ provider = client.llm_providers.update('llm_abc123', name: 'OpenAI Staging')
341
+
342
+ # Delete a provider
343
+ client.llm_providers.delete('llm_abc123')
344
+ ```
345
+
346
+ ### LLM projects
347
+
348
+ Projects are discovered automatically when syncing a provider. Assign them to clients to attribute costs.
349
+
350
+ ```ruby
351
+ # List projects for a provider
352
+ client.llm_projects.list('llm_abc123').each do |project|
353
+ puts "#{project.name}: #{project.total_cost_cents}c (#{project.client_name})"
354
+ end
355
+
356
+ # Assign a project to a client
357
+ project = client.llm_projects.update(
358
+ 'llm_abc123', 'proj_456', client_id: 'com_abc123'
359
+ )
360
+ ```
361
+
362
+ ### LLM costs
363
+
364
+ ```ruby
365
+ # Get cost summary (defaults to last 7 days)
366
+ costs = client.llm_costs.summary
367
+ puts "Total: $#{'%.2f' % (costs.data.summary.total_cost_cents / 100.0)}"
368
+ puts "Tokens: #{costs.data.summary.total_tokens}"
369
+
370
+ # Breakdown by provider
371
+ costs.data.providers.each do |p|
372
+ puts " #{p.name}: $#{'%.2f' % (p.cost_cents / 100.0)}"
373
+ end
374
+
375
+ # Breakdown by model
376
+ costs.data.models.each do |m|
377
+ puts " #{m.model}: $#{'%.2f' % (m.cost_cents / 100.0)}"
378
+ end
379
+
380
+ # Daily trend
381
+ costs.data.daily.each do |day|
382
+ puts " #{day.date}: $#{'%.2f' % (day.cost_cents / 100.0)}"
383
+ end
384
+
385
+ # Custom date range
386
+ costs = client.llm_costs.summary(
387
+ start_date: '2025-01-01',
388
+ end_date: '2025-01-31'
389
+ )
390
+
391
+ # Costs by client
392
+ client.llm_costs.by_client.data.each do |entry|
393
+ puts "#{entry.name}: $#{'%.2f' % (entry.cost_cents / 100.0)}"
394
+ end
395
+
396
+ # Costs by provider
397
+ client.llm_costs.by_provider.data.each do |entry|
398
+ puts "#{entry.name}: $#{'%.2f' % (entry.cost_cents / 100.0)}"
399
+ end
400
+ ```
401
+
402
+ ## Pagination
403
+
404
+ All `.list` methods return an `Enumerable` iterator that handles pagination automatically. By default, the API returns 25 items per page (max 100).
405
+
406
+ ```ruby
407
+ # Auto-paginate through all results
408
+ client.clients.list.each do |c|
409
+ puts c.name
410
+ end
411
+
412
+ # Control page size
413
+ client.clients.list(per_page: 100).each do |c|
414
+ puts c.name
415
+ end
416
+
417
+ # Get a single page
418
+ page = client.clients.list(per_page: 10).first_page
419
+ puts "#{page.meta.total} total, #{page.meta.total_pages} pages"
420
+ page.each do |c|
421
+ puts c.name
422
+ end
423
+
424
+ # Use any Enumerable method
425
+ names = client.clients.list.map(&:name)
426
+ active = client.workflows.list.select(&:is_active)
427
+ first_five = client.users.list.first(5)
428
+ ```
429
+
430
+ ## Error handling
431
+
432
+ The SDK raises typed exceptions for all API errors:
433
+
434
+ ```ruby
435
+ require 'administrate-sdk'
436
+
437
+ client = Administrate.new(api_key: 'sk_live_...')
438
+
439
+ begin
440
+ c = client.clients.get('com_nonexistent')
441
+ rescue Administrate::NotFoundError => e
442
+ puts "Not found: #{e.message}"
443
+ rescue Administrate::AuthenticationError
444
+ puts 'Invalid API key'
445
+ rescue Administrate::RateLimitError => e
446
+ puts "Rate limited. Retry after #{e.retry_after}s"
447
+ rescue Administrate::ValidationError => e
448
+ puts "Invalid params: #{e.body}"
449
+ rescue Administrate::APIError => e
450
+ puts "API error #{e.status_code}: #{e.message}"
451
+ end
452
+ ```
453
+
454
+ **Exception hierarchy:**
455
+
456
+ | Exception | Status code | Description |
457
+ |---|---|---|
458
+ | `Administrate::Error` | -- | Base exception for all SDK errors |
459
+ | `Administrate::APIError` | Any non-2xx | Base for all HTTP API errors |
460
+ | `Administrate::AuthenticationError` | 401 | Invalid or missing API key |
461
+ | `Administrate::PermissionDeniedError` | 403 | Insufficient token permissions |
462
+ | `Administrate::NotFoundError` | 404 | Resource does not exist |
463
+ | `Administrate::ValidationError` | 422 | Invalid request parameters |
464
+ | `Administrate::RateLimitError` | 429 | Rate limit exceeded (has `retry_after`) |
465
+ | `Administrate::InternalServerError` | 5xx | Server-side error |
466
+ | `Administrate::ConnectionError` | -- | Failed to connect to the API |
467
+ | `Administrate::TimeoutError` | -- | Request timed out |
468
+
469
+ All `APIError` subclasses expose `status_code`, `response` (the raw Faraday response), and `body` (parsed JSON or text).
470
+
471
+ ## Retries
472
+
473
+ The SDK automatically retries failed requests with exponential backoff:
474
+
475
+ - **429 (rate limited)** -- Retries after the duration specified in the `Retry-After` header
476
+ - **5xx (server errors)** -- Retries with exponential backoff (0.5s, 1s, 2s, ...)
477
+ - **Connection errors and timeouts** -- Retried with the same backoff schedule
478
+
479
+ By default, the SDK retries up to 3 times. Set `max_retries: 0` to disable:
480
+
481
+ ```ruby
482
+ client = Administrate.new(api_key: 'sk_live_...', max_retries: 0)
483
+ ```
484
+
485
+ ## Requirements
486
+
487
+ - Ruby 3.1+
488
+ - [faraday](https://lostisland.github.io/faraday/) >= 2.0
489
+ - [faraday-retry](https://github.com/lostisland/faraday-retry) >= 2.0
490
+
491
+ ## License
492
+
493
+ MIT
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administrate
4
+ class BaseModel
5
+ class << self
6
+ def attribute(*names)
7
+ @attributes ||= []
8
+ names.each do |name|
9
+ @attributes << name.to_sym
10
+ attr_reader name
11
+ end
12
+ end
13
+
14
+ def nested(name, klass, array: false)
15
+ @nested_fields ||= {}
16
+ @nested_fields[name.to_sym] = { klass: klass, array: array }
17
+ attr_reader name
18
+ end
19
+
20
+ def attributes
21
+ @attributes || []
22
+ end
23
+
24
+ def nested_fields
25
+ @nested_fields || {}
26
+ end
27
+
28
+ def inherited(subclass)
29
+ super
30
+ subclass.instance_variable_set(:@attributes, (attributes || []).dup)
31
+ subclass.instance_variable_set(:@nested_fields, (nested_fields || {}).dup)
32
+ end
33
+ end
34
+
35
+ attr_reader :extra
36
+
37
+ def initialize(data = {})
38
+ data = data.transform_keys(&:to_sym) if data.is_a?(Hash)
39
+ known_keys = self.class.attributes + self.class.nested_fields.keys
40
+ @extra = {}
41
+
42
+ known_keys.each do |key|
43
+ value = data[key]
44
+ nested_config = self.class.nested_fields[key]
45
+
46
+ if nested_config && !value.nil?
47
+ value = if nested_config[:array]
48
+ Array(value).map { |item| item.is_a?(Hash) ? nested_config[:klass].new(item) : item }
49
+ elsif value.is_a?(Hash)
50
+ nested_config[:klass].new(value)
51
+ else
52
+ value
53
+ end
54
+ end
55
+
56
+ instance_variable_set(:"@#{key}", value)
57
+ end
58
+
59
+ data.each do |key, value|
60
+ @extra[key] = value unless known_keys.include?(key.to_sym)
61
+ end
62
+ end
63
+
64
+ def to_h
65
+ result = {}
66
+ (self.class.attributes + self.class.nested_fields.keys).each do |key|
67
+ value = instance_variable_get(:"@#{key}")
68
+ result[key] = serialize_value(value)
69
+ end
70
+ result.merge(@extra)
71
+ end
72
+
73
+ def [](key)
74
+ key = key.to_sym
75
+ if (self.class.attributes + self.class.nested_fields.keys).include?(key)
76
+ instance_variable_get(:"@#{key}")
77
+ else
78
+ @extra[key]
79
+ end
80
+ end
81
+
82
+ def ==(other)
83
+ return false unless other.is_a?(self.class)
84
+
85
+ to_h == other.to_h
86
+ end
87
+
88
+ def inspect
89
+ attrs = (self.class.attributes + self.class.nested_fields.keys).map do |key|
90
+ "#{key}=#{instance_variable_get(:"@#{key}").inspect}"
91
+ end
92
+ "#<#{self.class.name} #{attrs.join(', ')}>"
93
+ end
94
+
95
+ private
96
+
97
+ def serialize_value(value)
98
+ case value
99
+ when BaseModel
100
+ value.to_h
101
+ when Array
102
+ value.map { |item| serialize_value(item) }
103
+ else
104
+ value
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administrate
4
+ class Client
5
+ attr_reader :account, :users, :webhooks, :api_tokens, :clients,
6
+ :instances, :workflows, :executions, :sync_runs,
7
+ :llm_providers, :llm_projects, :llm_costs
8
+
9
+ def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES)
10
+ config = Configuration.new(api_key: api_key, base_url: base_url, timeout: timeout, max_retries: max_retries)
11
+ transport = Transport.new(config)
12
+
13
+ @account = Resources::Account.new(transport)
14
+ @users = Resources::Users.new(transport)
15
+ @webhooks = Resources::Webhooks.new(transport)
16
+ @api_tokens = Resources::ApiTokens.new(transport)
17
+ @clients = Resources::Clients.new(transport)
18
+ @instances = Resources::Instances.new(transport)
19
+ @workflows = Resources::Workflows.new(transport)
20
+ @executions = Resources::Executions.new(transport)
21
+ @sync_runs = Resources::SyncRuns.new(transport)
22
+ @llm_providers = Resources::LlmProviders.new(transport)
23
+ @llm_projects = Resources::LlmProjects.new(transport)
24
+ @llm_costs = Resources::LlmCosts.new(transport)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administrate
4
+ DEFAULT_BASE_URL = 'https://administrate.dev'
5
+ API_VERSION = 'v1'
6
+ DEFAULT_TIMEOUT = 30
7
+ DEFAULT_MAX_RETRIES = 3
8
+
9
+ class Configuration
10
+ attr_reader :api_key, :base_url, :timeout, :max_retries
11
+
12
+ def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES)
13
+ raise Administrate::Error, 'api_key is required' if api_key.nil? || api_key.empty?
14
+ raise Administrate::Error, "api_key must start with 'sk_live_'" unless api_key.start_with?('sk_live_')
15
+
16
+ @api_key = api_key
17
+ @base_url = base_url
18
+ @timeout = timeout
19
+ @max_retries = max_retries
20
+ end
21
+ end
22
+ end