fluyenta-ruby 0.1.14

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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +11 -0
  4. data/README.md +571 -0
  5. data/lib/brainzlab/beacon/client.rb +227 -0
  6. data/lib/brainzlab/beacon/provisioner.rb +44 -0
  7. data/lib/brainzlab/beacon.rb +215 -0
  8. data/lib/brainzlab/configuration.rb +676 -0
  9. data/lib/brainzlab/context.rb +90 -0
  10. data/lib/brainzlab/cortex/cache.rb +59 -0
  11. data/lib/brainzlab/cortex/client.rb +159 -0
  12. data/lib/brainzlab/cortex/provisioner.rb +49 -0
  13. data/lib/brainzlab/cortex.rb +223 -0
  14. data/lib/brainzlab/debug.rb +305 -0
  15. data/lib/brainzlab/dendrite/client.rb +250 -0
  16. data/lib/brainzlab/dendrite/provisioner.rb +44 -0
  17. data/lib/brainzlab/dendrite.rb +195 -0
  18. data/lib/brainzlab/development/logger.rb +150 -0
  19. data/lib/brainzlab/development/store.rb +121 -0
  20. data/lib/brainzlab/development.rb +72 -0
  21. data/lib/brainzlab/devtools/assets/devtools.css +1329 -0
  22. data/lib/brainzlab/devtools/assets/devtools.js +396 -0
  23. data/lib/brainzlab/devtools/assets/logo.svg +6 -0
  24. data/lib/brainzlab/devtools/assets/templates/debug_panel.html.erb +511 -0
  25. data/lib/brainzlab/devtools/assets/templates/error_page.html.erb +1086 -0
  26. data/lib/brainzlab/devtools/data/collector.rb +248 -0
  27. data/lib/brainzlab/devtools/middleware/asset_server.rb +63 -0
  28. data/lib/brainzlab/devtools/middleware/database_handler.rb +177 -0
  29. data/lib/brainzlab/devtools/middleware/debug_panel.rb +126 -0
  30. data/lib/brainzlab/devtools/middleware/error_page.rb +377 -0
  31. data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +159 -0
  32. data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +98 -0
  33. data/lib/brainzlab/devtools.rb +75 -0
  34. data/lib/brainzlab/errors.rb +490 -0
  35. data/lib/brainzlab/flux/buffer.rb +96 -0
  36. data/lib/brainzlab/flux/client.rb +68 -0
  37. data/lib/brainzlab/flux/provisioner.rb +124 -0
  38. data/lib/brainzlab/flux.rb +184 -0
  39. data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
  40. data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
  41. data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
  42. data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
  43. data/lib/brainzlab/instrumentation/action_mailer.rb +182 -0
  44. data/lib/brainzlab/instrumentation/action_view.rb +380 -0
  45. data/lib/brainzlab/instrumentation/active_job.rb +569 -0
  46. data/lib/brainzlab/instrumentation/active_record.rb +559 -0
  47. data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
  48. data/lib/brainzlab/instrumentation/active_support_cache.rb +730 -0
  49. data/lib/brainzlab/instrumentation/aws.rb +183 -0
  50. data/lib/brainzlab/instrumentation/dalli.rb +108 -0
  51. data/lib/brainzlab/instrumentation/delayed_job.rb +234 -0
  52. data/lib/brainzlab/instrumentation/elasticsearch.rb +209 -0
  53. data/lib/brainzlab/instrumentation/excon.rb +152 -0
  54. data/lib/brainzlab/instrumentation/faraday.rb +181 -0
  55. data/lib/brainzlab/instrumentation/good_job.rb +102 -0
  56. data/lib/brainzlab/instrumentation/grape.rb +293 -0
  57. data/lib/brainzlab/instrumentation/graphql.rb +252 -0
  58. data/lib/brainzlab/instrumentation/httparty.rb +193 -0
  59. data/lib/brainzlab/instrumentation/mongodb.rb +187 -0
  60. data/lib/brainzlab/instrumentation/net_http.rb +114 -0
  61. data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
  62. data/lib/brainzlab/instrumentation/railties.rb +134 -0
  63. data/lib/brainzlab/instrumentation/redis.rb +324 -0
  64. data/lib/brainzlab/instrumentation/resque.rb +114 -0
  65. data/lib/brainzlab/instrumentation/sidekiq.rb +265 -0
  66. data/lib/brainzlab/instrumentation/solid_queue.rb +194 -0
  67. data/lib/brainzlab/instrumentation/stripe.rb +163 -0
  68. data/lib/brainzlab/instrumentation/typhoeus.rb +106 -0
  69. data/lib/brainzlab/instrumentation.rb +360 -0
  70. data/lib/brainzlab/nerve/client.rb +235 -0
  71. data/lib/brainzlab/nerve/provisioner.rb +44 -0
  72. data/lib/brainzlab/nerve.rb +219 -0
  73. data/lib/brainzlab/pulse/client.rb +203 -0
  74. data/lib/brainzlab/pulse/instrumentation.rb +401 -0
  75. data/lib/brainzlab/pulse/propagation.rb +241 -0
  76. data/lib/brainzlab/pulse/provisioner.rb +114 -0
  77. data/lib/brainzlab/pulse/tracer.rb +111 -0
  78. data/lib/brainzlab/pulse.rb +294 -0
  79. data/lib/brainzlab/rails/log_formatter.rb +807 -0
  80. data/lib/brainzlab/rails/log_subscriber.rb +334 -0
  81. data/lib/brainzlab/rails/railtie.rb +606 -0
  82. data/lib/brainzlab/recall/buffer.rb +66 -0
  83. data/lib/brainzlab/recall/client.rb +158 -0
  84. data/lib/brainzlab/recall/logger.rb +116 -0
  85. data/lib/brainzlab/recall/provisioner.rb +130 -0
  86. data/lib/brainzlab/recall.rb +175 -0
  87. data/lib/brainzlab/reflex/breadcrumbs.rb +55 -0
  88. data/lib/brainzlab/reflex/client.rb +150 -0
  89. data/lib/brainzlab/reflex/provisioner.rb +116 -0
  90. data/lib/brainzlab/reflex.rb +421 -0
  91. data/lib/brainzlab/sentinel/client.rb +236 -0
  92. data/lib/brainzlab/sentinel/provisioner.rb +44 -0
  93. data/lib/brainzlab/sentinel.rb +165 -0
  94. data/lib/brainzlab/signal/client.rb +60 -0
  95. data/lib/brainzlab/signal/provisioner.rb +115 -0
  96. data/lib/brainzlab/signal.rb +136 -0
  97. data/lib/brainzlab/synapse/client.rb +308 -0
  98. data/lib/brainzlab/synapse/provisioner.rb +44 -0
  99. data/lib/brainzlab/synapse.rb +270 -0
  100. data/lib/brainzlab/testing/event_store.rb +377 -0
  101. data/lib/brainzlab/testing/helpers.rb +650 -0
  102. data/lib/brainzlab/testing/matchers.rb +391 -0
  103. data/lib/brainzlab/testing.rb +327 -0
  104. data/lib/brainzlab/utilities/circuit_breaker.rb +290 -0
  105. data/lib/brainzlab/utilities/health_check.rb +294 -0
  106. data/lib/brainzlab/utilities/log_formatter.rb +254 -0
  107. data/lib/brainzlab/utilities/rate_limiter.rb +230 -0
  108. data/lib/brainzlab/utilities.rb +17 -0
  109. data/lib/brainzlab/vault/cache.rb +80 -0
  110. data/lib/brainzlab/vault/client.rb +216 -0
  111. data/lib/brainzlab/vault/provisioner.rb +49 -0
  112. data/lib/brainzlab/vault.rb +262 -0
  113. data/lib/brainzlab/version.rb +5 -0
  114. data/lib/brainzlab/vision/client.rb +175 -0
  115. data/lib/brainzlab/vision/provisioner.rb +136 -0
  116. data/lib/brainzlab/vision.rb +155 -0
  117. data/lib/brainzlab-sdk.rb +3 -0
  118. data/lib/brainzlab.rb +306 -0
  119. data/lib/generators/brainzlab/install/install_generator.rb +63 -0
  120. data/lib/generators/brainzlab/install/templates/brainzlab.rb.tt +77 -0
  121. metadata +251 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'signal/client'
4
+ require_relative 'signal/provisioner'
5
+
6
+ module BrainzLab
7
+ module Signal
8
+ SEVERITIES = %i[info warning error critical].freeze
9
+
10
+ class << self
11
+ # Send an alert with a message
12
+ # @param name [String] Alert name (e.g., 'high_error_rate', 'low_disk_space')
13
+ # @param message [String] Alert message
14
+ # @param severity [Symbol] Alert severity (:info, :warning, :error, :critical)
15
+ # @param channels [Array<String>] Channels to send alert to (e.g., ['slack', 'email'])
16
+ # @param data [Hash] Additional data to include with the alert
17
+ def alert(name, message, severity: :warning, channels: nil, data: {})
18
+ return unless enabled?
19
+
20
+ ensure_provisioned!
21
+ return unless BrainzLab.configuration.signal_valid?
22
+
23
+ payload = {
24
+ type: 'alert',
25
+ name: name,
26
+ message: message,
27
+ severity: severity.to_s,
28
+ channels: channels,
29
+ data: data,
30
+ timestamp: Time.now.utc.iso8601(3),
31
+ environment: BrainzLab.configuration.environment,
32
+ service: BrainzLab.configuration.service,
33
+ host: BrainzLab.configuration.host,
34
+ context: context_data
35
+ }
36
+
37
+ client.send_alert(payload)
38
+ end
39
+
40
+ # Send a notification to specific channels
41
+ # @param channel [String, Array<String>] Channel(s) to send to ('slack', 'email', 'webhook')
42
+ # @param message [String] Notification message
43
+ # @param title [String] Optional notification title
44
+ # @param data [Hash] Additional data
45
+ def notify(channel, message, title: nil, data: {})
46
+ return unless enabled?
47
+
48
+ ensure_provisioned!
49
+ return unless BrainzLab.configuration.signal_valid?
50
+
51
+ channels = Array(channel)
52
+ payload = {
53
+ type: 'notification',
54
+ channels: channels,
55
+ message: message,
56
+ title: title,
57
+ data: data,
58
+ timestamp: Time.now.utc.iso8601(3),
59
+ environment: BrainzLab.configuration.environment,
60
+ service: BrainzLab.configuration.service
61
+ }
62
+
63
+ client.send_notification(payload)
64
+ end
65
+
66
+ # Trigger a predefined alert rule
67
+ # @param rule_name [String] Name of the alert rule to trigger
68
+ # @param context [Hash] Context data to pass to the rule
69
+ def trigger(rule_name, context = {})
70
+ return unless enabled?
71
+
72
+ ensure_provisioned!
73
+ return unless BrainzLab.configuration.signal_valid?
74
+
75
+ payload = {
76
+ type: 'trigger',
77
+ rule: rule_name,
78
+ context: context,
79
+ timestamp: Time.now.utc.iso8601(3),
80
+ environment: BrainzLab.configuration.environment,
81
+ service: BrainzLab.configuration.service
82
+ }
83
+
84
+ client.trigger_rule(payload)
85
+ end
86
+
87
+ # Send a test alert to verify configuration
88
+ def test!
89
+ alert(
90
+ 'test_alert',
91
+ 'This is a test alert from BrainzLab Signal SDK',
92
+ severity: :info,
93
+ data: { test: true, sdk_version: BrainzLab::VERSION }
94
+ )
95
+ end
96
+
97
+ # === INTERNAL ===
98
+
99
+ def ensure_provisioned!
100
+ return if @provisioned
101
+
102
+ @provisioned = true
103
+ provisioner.ensure_project!
104
+ end
105
+
106
+ def provisioner
107
+ @provisioner ||= Provisioner.new(BrainzLab.configuration)
108
+ end
109
+
110
+ def client
111
+ @client ||= Client.new(BrainzLab.configuration)
112
+ end
113
+
114
+ def reset!
115
+ @client = nil
116
+ @provisioner = nil
117
+ @provisioned = false
118
+ end
119
+
120
+ private
121
+
122
+ def enabled?
123
+ BrainzLab.configuration.signal_effectively_enabled?
124
+ end
125
+
126
+ def context_data
127
+ ctx = BrainzLab::Context.current
128
+ {
129
+ user: ctx.user,
130
+ tags: ctx.tags,
131
+ extra: ctx.extra
132
+ }
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module BrainzLab
8
+ module Synapse
9
+ class Client
10
+ def initialize(config)
11
+ @config = config
12
+ @base_url = config.synapse_url || 'https://synapse.brainzlab.ai'
13
+ end
14
+
15
+ # List all projects
16
+ def list_projects(status: nil, page: 1, per_page: 20)
17
+ params = { page: page, per_page: per_page }
18
+ params[:status] = status if status
19
+
20
+ response = request(:get, '/api/v1/projects', params: params)
21
+
22
+ return [] unless response.is_a?(Net::HTTPSuccess)
23
+
24
+ data = JSON.parse(response.body, symbolize_names: true)
25
+ data[:projects] || []
26
+ rescue StandardError => e
27
+ log_error('list_projects', e)
28
+ []
29
+ end
30
+
31
+ # Get project details
32
+ def get_project(project_id)
33
+ response = request(:get, "/api/v1/projects/#{project_id}")
34
+
35
+ return nil unless response.is_a?(Net::HTTPSuccess)
36
+
37
+ JSON.parse(response.body, symbolize_names: true)
38
+ rescue StandardError => e
39
+ log_error('get_project', e)
40
+ nil
41
+ end
42
+
43
+ # Create a new project
44
+ def create_project(name:, repos: [], description: nil, **options)
45
+ response = request(
46
+ :post,
47
+ '/api/v1/projects',
48
+ body: {
49
+ name: name,
50
+ description: description,
51
+ repos: repos,
52
+ **options
53
+ }
54
+ )
55
+
56
+ return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
57
+
58
+ JSON.parse(response.body, symbolize_names: true)
59
+ rescue StandardError => e
60
+ log_error('create_project', e)
61
+ nil
62
+ end
63
+
64
+ # Start project containers
65
+ def start_project(project_id)
66
+ response = request(:post, "/api/v1/projects/#{project_id}/up")
67
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
68
+ rescue StandardError => e
69
+ log_error('start_project', e)
70
+ false
71
+ end
72
+
73
+ # Stop project containers
74
+ def stop_project(project_id)
75
+ response = request(:post, "/api/v1/projects/#{project_id}/down")
76
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
77
+ rescue StandardError => e
78
+ log_error('stop_project', e)
79
+ false
80
+ end
81
+
82
+ # Restart project containers
83
+ def restart_project(project_id)
84
+ response = request(:post, "/api/v1/projects/#{project_id}/restart")
85
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
86
+ rescue StandardError => e
87
+ log_error('restart_project', e)
88
+ false
89
+ end
90
+
91
+ # Deploy project to environment
92
+ def deploy(project_id, environment:, options: {})
93
+ response = request(
94
+ :post,
95
+ "/api/v1/projects/#{project_id}/deploy",
96
+ body: {
97
+ environment: environment,
98
+ **options
99
+ }
100
+ )
101
+
102
+ return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
103
+
104
+ JSON.parse(response.body, symbolize_names: true)
105
+ rescue StandardError => e
106
+ log_error('deploy', e)
107
+ nil
108
+ end
109
+
110
+ # Get deployment status
111
+ def get_deployment(deployment_id)
112
+ response = request(:get, "/api/v1/deployments/#{deployment_id}")
113
+
114
+ return nil unless response.is_a?(Net::HTTPSuccess)
115
+
116
+ JSON.parse(response.body, symbolize_names: true)
117
+ rescue StandardError => e
118
+ log_error('get_deployment', e)
119
+ nil
120
+ end
121
+
122
+ # Create an AI task
123
+ def create_task(project_id:, description:, type: nil, priority: nil, **options)
124
+ response = request(
125
+ :post,
126
+ '/api/v1/tasks',
127
+ body: {
128
+ project_id: project_id,
129
+ description: description,
130
+ type: type,
131
+ priority: priority,
132
+ **options
133
+ }
134
+ )
135
+
136
+ return nil unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
137
+
138
+ JSON.parse(response.body, symbolize_names: true)
139
+ rescue StandardError => e
140
+ log_error('create_task', e)
141
+ nil
142
+ end
143
+
144
+ # Get task status
145
+ def get_task(task_id)
146
+ response = request(:get, "/api/v1/tasks/#{task_id}")
147
+
148
+ return nil unless response.is_a?(Net::HTTPSuccess)
149
+
150
+ JSON.parse(response.body, symbolize_names: true)
151
+ rescue StandardError => e
152
+ log_error('get_task', e)
153
+ nil
154
+ end
155
+
156
+ # Get task status and progress
157
+ def get_task_status(task_id)
158
+ response = request(:get, "/api/v1/tasks/#{task_id}/status")
159
+
160
+ return nil unless response.is_a?(Net::HTTPSuccess)
161
+
162
+ JSON.parse(response.body, symbolize_names: true)
163
+ rescue StandardError => e
164
+ log_error('get_task_status', e)
165
+ nil
166
+ end
167
+
168
+ # List tasks for a project
169
+ def list_tasks(project_id: nil, status: nil, page: 1, per_page: 20)
170
+ params = { page: page, per_page: per_page }
171
+ params[:project_id] = project_id if project_id
172
+ params[:status] = status if status
173
+
174
+ response = request(:get, '/api/v1/tasks', params: params)
175
+
176
+ return [] unless response.is_a?(Net::HTTPSuccess)
177
+
178
+ data = JSON.parse(response.body, symbolize_names: true)
179
+ data[:tasks] || []
180
+ rescue StandardError => e
181
+ log_error('list_tasks', e)
182
+ []
183
+ end
184
+
185
+ # Cancel a running task
186
+ def cancel_task(task_id)
187
+ response = request(:post, "/api/v1/tasks/#{task_id}/cancel")
188
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
189
+ rescue StandardError => e
190
+ log_error('cancel_task', e)
191
+ false
192
+ end
193
+
194
+ # Get container logs
195
+ def get_logs(project_id, container: nil, lines: 100, since: nil)
196
+ params = { lines: lines }
197
+ params[:container] = container if container
198
+ params[:since] = since if since
199
+
200
+ response = request(:get, "/api/v1/projects/#{project_id}/logs", params: params)
201
+
202
+ return nil unless response.is_a?(Net::HTTPSuccess)
203
+
204
+ JSON.parse(response.body, symbolize_names: true)
205
+ rescue StandardError => e
206
+ log_error('get_logs', e)
207
+ nil
208
+ end
209
+
210
+ # Execute command in container
211
+ def exec(project_id, command:, container: nil, timeout: 30)
212
+ response = request(
213
+ :post,
214
+ "/api/v1/projects/#{project_id}/exec",
215
+ body: {
216
+ command: command,
217
+ container: container,
218
+ timeout: timeout
219
+ }
220
+ )
221
+
222
+ return nil unless response.is_a?(Net::HTTPSuccess)
223
+
224
+ JSON.parse(response.body, symbolize_names: true)
225
+ rescue StandardError => e
226
+ log_error('exec', e)
227
+ nil
228
+ end
229
+
230
+ def provision(project_id:, app_name:)
231
+ response = request(
232
+ :post,
233
+ '/api/v1/projects/provision',
234
+ body: { project_id: project_id, app_name: app_name },
235
+ use_service_key: true
236
+ )
237
+
238
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated)
239
+ rescue StandardError => e
240
+ log_error('provision', e)
241
+ false
242
+ end
243
+
244
+ private
245
+
246
+ def request(method, path, headers: {}, body: nil, params: nil, use_service_key: false)
247
+ uri = URI.parse("#{@base_url}#{path}")
248
+
249
+ uri.query = URI.encode_www_form(params) if params
250
+
251
+ http = Net::HTTP.new(uri.host, uri.port)
252
+ http.use_ssl = uri.scheme == 'https'
253
+ http.open_timeout = 10
254
+ http.read_timeout = 120 # Longer timeout for AI tasks
255
+
256
+ request = case method
257
+ when :get
258
+ Net::HTTP::Get.new(uri)
259
+ when :post
260
+ Net::HTTP::Post.new(uri)
261
+ when :put
262
+ Net::HTTP::Put.new(uri)
263
+ when :delete
264
+ Net::HTTP::Delete.new(uri)
265
+ end
266
+
267
+ request['Content-Type'] = 'application/json'
268
+ request['Accept'] = 'application/json'
269
+
270
+ if use_service_key
271
+ request['X-Service-Key'] = @config.synapse_master_key || @config.secret_key
272
+ else
273
+ auth_key = @config.synapse_api_key || @config.secret_key
274
+ request['Authorization'] = "Bearer #{auth_key}" if auth_key
275
+ end
276
+
277
+ headers.each { |k, v| request[k] = v }
278
+ request.body = body.to_json if body
279
+
280
+ http.request(request)
281
+ end
282
+
283
+ def log_error(operation, error)
284
+ structured_error = ErrorHandler.wrap(error, service: 'Synapse', operation: operation)
285
+ BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{structured_error.message}")
286
+
287
+ # Call on_error callback if configured
288
+ if @config.on_error
289
+ @config.on_error.call(structured_error, { service: 'Synapse', operation: operation })
290
+ end
291
+ end
292
+
293
+ def handle_response_error(response, operation)
294
+ return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPCreated) || response.is_a?(Net::HTTPNoContent) || response.is_a?(Net::HTTPAccepted)
295
+
296
+ structured_error = ErrorHandler.from_response(response, service: 'Synapse', operation: operation)
297
+ BrainzLab.debug_log("[Synapse::Client] #{operation} failed: #{structured_error.message}")
298
+
299
+ # Call on_error callback if configured
300
+ if @config.on_error
301
+ @config.on_error.call(structured_error, { service: 'Synapse', operation: operation })
302
+ end
303
+
304
+ structured_error
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Synapse
5
+ class Provisioner
6
+ def initialize(config)
7
+ @config = config
8
+ @provisioned = false
9
+ end
10
+
11
+ def ensure_project!
12
+ return if @provisioned
13
+ return unless @config.synapse_auto_provision
14
+ return unless valid_auth?
15
+
16
+ @provisioned = true
17
+
18
+ project_id = detect_project_id
19
+ return unless project_id
20
+
21
+ client = Client.new(@config)
22
+ client.provision(
23
+ project_id: project_id,
24
+ app_name: @config.app_name || @config.service
25
+ )
26
+
27
+ BrainzLab.debug_log("[Synapse::Provisioner] Project provisioned: #{project_id}")
28
+ rescue StandardError => e
29
+ BrainzLab.debug_log("[Synapse::Provisioner] Provisioning failed: #{e.message}")
30
+ end
31
+
32
+ private
33
+
34
+ def valid_auth?
35
+ key = @config.synapse_api_key || @config.synapse_master_key || @config.secret_key
36
+ !key.nil? && !key.empty?
37
+ end
38
+
39
+ def detect_project_id
40
+ ENV.fetch('BRAINZLAB_PROJECT_ID', nil)
41
+ end
42
+ end
43
+ end
44
+ end