hokipoki 0.1.2 → 0.1.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,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokipoki
4
+ module Claude
5
+ # Claude Parasite - Intelligent Claude CLI integration with HiveMind
6
+ # Automatically injects vector intelligence context into Claude conversations
7
+ class Parasite
8
+ include Singleton
9
+
10
+ def initialize
11
+ @logger = Rails.logger
12
+ @feedback = Feedback::DisplayManager.instance
13
+ @connection_manager = ConnectionManager.instance
14
+ @injection_stats = {
15
+ total_injections: 0,
16
+ successful_injections: 0,
17
+ failed_injections: 0,
18
+ total_tokens_saved: 0
19
+ }
20
+ end
21
+
22
+ # Auto-load when Claude CLI starts
23
+ def self.auto_load!
24
+ instance.ensure_connection!
25
+ instance.display_startup_message
26
+ instance
27
+ end
28
+
29
+ # Check if should inject context for this request
30
+ def should_inject?(user_message, request_context = {})
31
+ return false unless @connection_manager.connected?
32
+ return false if simple_greeting?(user_message)
33
+ return false if user_message.length < 10
34
+
35
+ # Smart injection logic based on content analysis
36
+ programming_related?(user_message) ||
37
+ question_asking?(user_message) ||
38
+ implementation_request?(user_message)
39
+ end
40
+
41
+ # Get injection strategy for this request
42
+ def injection_strategy(user_message, request_context = {})
43
+ intent = analyze_intent(user_message)
44
+ token_budget = calculate_token_budget(user_message, intent)
45
+
46
+ {
47
+ max_tokens: token_budget,
48
+ style: :contextual_intelligence,
49
+ intent: intent,
50
+ compression: true,
51
+ priority: determine_priority(intent),
52
+ bypass_cache: emergency_request?(user_message)
53
+ }
54
+ end
55
+
56
+ # Craft the injection content
57
+ def craft_injection(user_message, original_messages, strategy = {})
58
+ return original_messages unless @connection_manager.connected?
59
+
60
+ @feedback.debug_info('injection', "analyzing message for context injection")
61
+
62
+ begin
63
+ # Get vector intelligence for the query
64
+ @feedback.pulling_from_hive_mind('context')
65
+ facts = retrieve_vector_intelligence(user_message, strategy)
66
+
67
+ if facts.present?
68
+ # Create intelligence-enhanced system message
69
+ system_message = build_system_message(facts, strategy)
70
+
71
+ # Calculate token savings
72
+ tokens_saved = estimate_tokens_saved(facts, user_message)
73
+
74
+ # Track successful injection
75
+ track_injection_success(user_message, facts, strategy, tokens_saved)
76
+
77
+ @feedback.total_tokens_saved(tokens_saved, @injection_stats[:total_tokens_saved])
78
+
79
+ # Add system message to conversation
80
+ [system_message] + original_messages
81
+ else
82
+ # No relevant context found, return original
83
+ @feedback.debug_info('injection', 'no relevant context found')
84
+ track_injection_attempt(user_message, 'no_context_found')
85
+ original_messages
86
+ end
87
+
88
+ rescue => e
89
+ @feedback.operation_error('injection', e.message)
90
+ @logger.error "🦠 Claude Parasite injection failed: #{e.message}"
91
+ track_injection_failure(user_message, e)
92
+ original_messages
93
+ end
94
+ end
95
+
96
+ # Display parasite status
97
+ def display_status
98
+ puts "\n#{Pastel.new.cyan.bold('🦠 Claude Parasite Status')}"
99
+ puts " Status: #{connection_status_display}"
100
+ puts " Total Injections: #{@injection_stats[:total_injections]}"
101
+ puts " Success Rate: #{success_rate}%"
102
+ puts " Tokens Saved: #{@injection_stats[:total_tokens_saved]}"
103
+ puts " Connection: #{@connection_manager.connected? ? '🟢 Online' : '🔴 Offline'}"
104
+ end
105
+
106
+ # Get parasite statistics
107
+ def statistics
108
+ @injection_stats.merge({
109
+ success_rate: success_rate,
110
+ connection_status: @connection_manager.connected?,
111
+ last_injection: @last_injection_time,
112
+ uptime: calculate_uptime
113
+ })
114
+ end
115
+
116
+ # Ensure connection to HiveMind
117
+ def ensure_connection!
118
+ unless @connection_manager.connected?
119
+ @connection_manager.establish_connection!
120
+ end
121
+ end
122
+
123
+ # Display startup message when Claude loads
124
+ def display_startup_message
125
+ pastel = Pastel.new
126
+
127
+ puts "\n#{pastel.green.bold('🧠 HiveMind is now connected!')}"
128
+ puts pastel.cyan(" 🦠 Claude Parasite: Active")
129
+ puts pastel.cyan(" 📊 Vector Intelligence: Online")
130
+ puts pastel.cyan(" ⚡ Smart Context Injection: Enabled")
131
+
132
+ if @connection_manager.connected?
133
+ db_status = @connection_manager.connection_status
134
+ puts pastel.dim(" 📈 Vector DB: #{db_status[:vector_db_status][:document_count]} documents ready")
135
+ end
136
+
137
+ puts pastel.yellow("\n💡 Your Claude responses are now enhanced with project-specific intelligence!")
138
+ puts pastel.dim(" The parasite will automatically inject relevant context from your vector database.\n")
139
+ end
140
+
141
+ private
142
+
143
+ def simple_greeting?(message)
144
+ greetings = %w[hi hello hey thanks thank you yes no ok okay sure]
145
+ message.downcase.strip.split.all? { |word| greetings.include?(word) }
146
+ end
147
+
148
+ def programming_related?(message)
149
+ programming_keywords = %w[
150
+ code function method class variable api database
151
+ implement create build debug fix error bug
152
+ css html javascript react vue angular rails ruby python
153
+ sql query table model controller view
154
+ git github commit push pull merge
155
+ deploy server docker kubernetes aws
156
+ ]
157
+
158
+ message.downcase.split.any? { |word| programming_keywords.include?(word) }
159
+ end
160
+
161
+ def question_asking?(message)
162
+ question_patterns = [
163
+ /\bhow\s+(do|can|to)\b/i,
164
+ /\bwhat\s+(is|are|does|should)\b/i,
165
+ /\bwhere\s+(is|are|do|should)\b/i,
166
+ /\bwhy\s+(is|are|does|should)\b/i,
167
+ /\bwhen\s+(is|are|do|should)\b/i,
168
+ /\bwhich\s+(is|are|should)\b/i,
169
+ /\?$/
170
+ ]
171
+
172
+ question_patterns.any? { |pattern| message.match?(pattern) }
173
+ end
174
+
175
+ def implementation_request?(message)
176
+ implementation_keywords = %w[
177
+ implement create build make generate add
178
+ show example tutorial guide step
179
+ help setup configure install
180
+ ]
181
+
182
+ message.downcase.split.any? { |word| implementation_keywords.include?(word) }
183
+ end
184
+
185
+ def analyze_intent(message)
186
+ return :debugging if message.match?(/\b(error|bug|fix|debug|problem|issue|broken)\b/i)
187
+ return :implementation if message.match?(/\b(implement|create|build|make|add)\b/i)
188
+ return :learning if message.match?(/\b(how|what|why|explain|understand|learn)\b/i)
189
+ return :reference if message.match?(/\b(example|show|demo|sample|reference)\b/i)
190
+ return :optimization if message.match?(/\b(optimize|improve|better|faster|performance)\b/i)
191
+
192
+ :general
193
+ end
194
+
195
+ def calculate_token_budget(message, intent)
196
+ base_budget = case intent
197
+ when :debugging then 2000 # More context for debugging
198
+ when :implementation then 1800 # Good context for implementation
199
+ when :learning then 1500 # Standard context for learning
200
+ when :reference then 1200 # Less context for references
201
+ when :optimization then 1600 # Moderate context for optimization
202
+ else 1000 # Minimal context for general
203
+ end
204
+
205
+ # Adjust based on message length
206
+ if message.length > 200
207
+ base_budget += 500
208
+ elsif message.length < 50
209
+ base_budget -= 300
210
+ end
211
+
212
+ [base_budget, 3000].min # Cap at 3000 tokens
213
+ end
214
+
215
+ def determine_priority(intent)
216
+ case intent
217
+ when :debugging then :high
218
+ when :implementation then :high
219
+ when :optimization then :medium
220
+ when :learning then :medium
221
+ when :reference then :low
222
+ else :low
223
+ end
224
+ end
225
+
226
+ def emergency_request?(message)
227
+ emergency_keywords = %w[urgent emergency critical broken down error fatal]
228
+ message.downcase.split.any? { |word| emergency_keywords.include?(word) }
229
+ end
230
+
231
+ def retrieve_vector_intelligence(message, strategy)
232
+ token_budget = strategy[:max_tokens] || 1500
233
+
234
+ # Use HokiPoki's smart retrieval with intent analysis
235
+ Hokipoki.retrieve_facts(
236
+ message,
237
+ token_budget: token_budget,
238
+ intent: strategy[:intent] || 'auto'
239
+ )
240
+ end
241
+
242
+ def build_system_message(facts, strategy)
243
+ intent = strategy[:intent] || :general
244
+
245
+ system_content = case intent
246
+ when :debugging
247
+ "Context for debugging: #{facts}\n\nFocus on identifying issues and providing solutions."
248
+ when :implementation
249
+ "Implementation context: #{facts}\n\nProvide concrete code examples and step-by-step guidance."
250
+ when :learning
251
+ "Learning context: #{facts}\n\nExplain concepts clearly with examples."
252
+ when :reference
253
+ "Reference information: #{facts}\n\nProvide accurate documentation and examples."
254
+ when :optimization
255
+ "Optimization context: #{facts}\n\nSuggest performance improvements and best practices."
256
+ else
257
+ "Context: #{facts}"
258
+ end
259
+
260
+ {
261
+ 'role' => 'system',
262
+ 'content' => system_content
263
+ }
264
+ end
265
+
266
+ def track_injection_success(message, facts, strategy, tokens_saved = nil)
267
+ @injection_stats[:total_injections] += 1
268
+ @injection_stats[:successful_injections] += 1
269
+
270
+ # Use provided tokens saved or estimate
271
+ estimated_tokens = tokens_saved || (facts.length / 4)
272
+ @injection_stats[:total_tokens_saved] += estimated_tokens
273
+
274
+ @last_injection_time = Time.current
275
+
276
+ @logger.info "🦠 Claude Parasite: Context injected successfully (#{estimated_tokens} tokens)"
277
+ end
278
+
279
+ def estimate_tokens_saved(facts, user_message)
280
+ # Estimate tokens saved by providing specific context vs generic response
281
+ context_tokens = facts.length / 4
282
+ baseline_tokens = user_message.length * 0.6 # Rough estimate of generic response
283
+ savings = [baseline_tokens - context_tokens, 0].max.round
284
+ savings
285
+ end
286
+
287
+ def track_injection_attempt(message, reason)
288
+ @injection_stats[:total_injections] += 1
289
+ @logger.debug "🦠 Claude Parasite: Injection attempted but skipped (#{reason})"
290
+ end
291
+
292
+ def track_injection_failure(message, error)
293
+ @injection_stats[:total_injections] += 1
294
+ @injection_stats[:failed_injections] += 1
295
+ @logger.error "🦠 Claude Parasite: Injection failed - #{error.message}"
296
+ end
297
+
298
+ def success_rate
299
+ return 0 if @injection_stats[:total_injections] == 0
300
+
301
+ (@injection_stats[:successful_injections].to_f / @injection_stats[:total_injections] * 100).round(1)
302
+ end
303
+
304
+ def connection_status_display
305
+ if @connection_manager.connected?
306
+ Pastel.new.green('🟢 Connected')
307
+ else
308
+ Pastel.new.red('🔴 Disconnected')
309
+ end
310
+ end
311
+
312
+ def calculate_uptime
313
+ return 'N/A' unless @connection_manager.instance_variable_get(:@connection_time)
314
+
315
+ uptime_seconds = Time.current - @connection_manager.instance_variable_get(:@connection_time)
316
+ format_uptime(uptime_seconds)
317
+ end
318
+
319
+ def format_uptime(seconds)
320
+ hours = (seconds / 3600).to_i
321
+ minutes = ((seconds % 3600) / 60).to_i
322
+
323
+ if hours > 0
324
+ "#{hours}h #{minutes}m"
325
+ elsif minutes > 0
326
+ "#{minutes}m"
327
+ else
328
+ "#{seconds.to_i}s"
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokipoki
4
+ class Configuration
5
+ attr_accessor :vector_dimensions,
6
+ :lm_studio_url,
7
+ :embedding_model,
8
+ :enable_parasites,
9
+ :enable_forge,
10
+ :enable_behavioral_analysis,
11
+ :enable_template_optimization,
12
+ :template_caching,
13
+ :token_budget_default,
14
+ :context_compression,
15
+ :offline_mode,
16
+ :api_authentication,
17
+ :audit_logging,
18
+ :encrypted_logging,
19
+ :rate_limiting,
20
+ :default_brain,
21
+ :auto_brain_switching,
22
+ :universal_parasite_generation,
23
+ :auto_optimization,
24
+ :cross_parasite_learning,
25
+ :workshop_interface,
26
+ :background_processing,
27
+ :multi_llm_orchestration,
28
+ :redis_url,
29
+ :cache_ttl,
30
+ :max_retries
31
+
32
+ def initialize
33
+ # === CORE SETTINGS ===
34
+ @vector_dimensions = 768
35
+ @lm_studio_url = ENV.fetch('LM_STUDIO_URL', 'http://localhost:1236')
36
+ @embedding_model = ENV.fetch('EMBEDDING_MODEL', 'text-embedding-nomic-embed-text-v1.5')
37
+
38
+ # === COMPONENT ACTIVATION ===
39
+ @enable_parasites = ENV.fetch('HOKIPOKI_ENABLE_PARASITES', 'true') == 'true'
40
+ @enable_forge = ENV.fetch('HOKIPOKI_ENABLE_FORGE', 'false') == 'true'
41
+ @enable_behavioral_analysis = ENV.fetch('HOKIPOKI_ENABLE_BEHAVIORAL_ANALYSIS', 'true') == 'true'
42
+ @enable_template_optimization = ENV.fetch('HOKIPOKI_ENABLE_TEMPLATE_OPTIMIZATION', 'true') == 'true'
43
+
44
+ # === PERFORMANCE ===
45
+ @template_caching = ENV.fetch('HOKIPOKI_TEMPLATE_CACHING', 'true') == 'true'
46
+ @token_budget_default = ENV.fetch('HOKIPOKI_TOKEN_BUDGET_DEFAULT', '1500').to_i
47
+ @context_compression = ENV.fetch('HOKIPOKI_CONTEXT_COMPRESSION', 'true') == 'true'
48
+ @offline_mode = ENV.fetch('HOKIPOKI_OFFLINE_MODE', 'false') == 'true'
49
+ @cache_ttl = ENV.fetch('HOKIPOKI_CACHE_TTL', '3600').to_i # 1 hour default
50
+ @max_retries = ENV.fetch('HOKIPOKI_MAX_RETRIES', '3').to_i
51
+
52
+ # === SECURITY ===
53
+ @api_authentication = ENV.fetch('HOKIPOKI_API_AUTHENTICATION', 'true') == 'true'
54
+ @audit_logging = ENV.fetch('HOKIPOKI_AUDIT_LOGGING', 'true') == 'true'
55
+ @encrypted_logging = ENV.fetch('HOKIPOKI_ENCRYPTED_LOGGING', 'false') == 'true'
56
+ @rate_limiting = ENV.fetch('HOKIPOKI_RATE_LIMITING', 'true') == 'true'
57
+
58
+ # === BRAINS CONFIGURATION ===
59
+ @default_brain = ENV.fetch('HOKIPOKI_DEFAULT_BRAIN', 'default_brain')
60
+ @auto_brain_switching = ENV.fetch('HOKIPOKI_AUTO_BRAIN_SWITCHING', 'true') == 'true'
61
+
62
+ # === PARASITE SYSTEM ===
63
+ @universal_parasite_generation = ENV.fetch('HOKIPOKI_UNIVERSAL_PARASITE_GENERATION', 'true') == 'true'
64
+ @auto_optimization = ENV.fetch('HOKIPOKI_AUTO_OPTIMIZATION', 'true') == 'true'
65
+ @cross_parasite_learning = ENV.fetch('HOKIPOKI_CROSS_PARASITE_LEARNING', 'true') == 'true'
66
+
67
+ # === FORGE CONFIGURATION ===
68
+ @workshop_interface = ENV.fetch('HOKIPOKI_WORKSHOP_INTERFACE', 'true') == 'true'
69
+ @background_processing = ENV.fetch('HOKIPOKI_BACKGROUND_PROCESSING', 'sidekiq').to_sym
70
+ @multi_llm_orchestration = ENV.fetch('HOKIPOKI_MULTI_LLM_ORCHESTRATION', 'true') == 'true'
71
+
72
+ # === REDIS CONFIGURATION ===
73
+ @redis_url = ENV.fetch('REDIS_URL', 'redis://localhost:6379/0')
74
+ end
75
+
76
+ def validate!
77
+ errors = []
78
+
79
+ errors << "vector_dimensions must be positive" if vector_dimensions <= 0
80
+ errors << "token_budget_default must be positive" if token_budget_default <= 0
81
+ errors << "lm_studio_url must be a valid URL" unless valid_url?(lm_studio_url)
82
+ errors << "redis_url must be a valid URL" unless valid_url?(redis_url)
83
+ errors << "cache_ttl must be positive" if cache_ttl <= 0
84
+ errors << "max_retries must be non-negative" if max_retries < 0
85
+
86
+ unless %i[sidekiq resque delayed_job].include?(background_processing)
87
+ errors << "background_processing must be one of: sidekiq, resque, delayed_job"
88
+ end
89
+
90
+ raise ConfigurationError, "Configuration errors: #{errors.join(', ')}" if errors.any?
91
+
92
+ true
93
+ end
94
+
95
+ def redis_config
96
+ {
97
+ url: redis_url,
98
+ timeout: 5,
99
+ reconnect_attempts: max_retries
100
+ }
101
+ end
102
+
103
+ def lm_studio_config
104
+ {
105
+ url: lm_studio_url,
106
+ timeout: 30,
107
+ retries: max_retries,
108
+ embedding_model: embedding_model
109
+ }
110
+ end
111
+
112
+ def vector_config
113
+ {
114
+ dimensions: vector_dimensions,
115
+ distance_metric: 'cosine',
116
+ index_type: 'hnsw'
117
+ }
118
+ end
119
+
120
+ def security_config
121
+ {
122
+ api_authentication: api_authentication,
123
+ audit_logging: audit_logging,
124
+ encrypted_logging: encrypted_logging,
125
+ rate_limiting: rate_limiting
126
+ }
127
+ end
128
+
129
+ def parasite_config
130
+ {
131
+ enabled: enable_parasites,
132
+ universal_generation: universal_parasite_generation,
133
+ auto_optimization: auto_optimization,
134
+ cross_learning: cross_parasite_learning,
135
+ behavioral_analysis: enable_behavioral_analysis
136
+ }
137
+ end
138
+
139
+ def forge_config
140
+ {
141
+ enabled: enable_forge,
142
+ workshop_interface: workshop_interface,
143
+ background_processing: background_processing,
144
+ multi_llm_orchestration: multi_llm_orchestration
145
+ }
146
+ end
147
+
148
+ def intelligence_config
149
+ {
150
+ template_optimization: enable_template_optimization,
151
+ template_caching: template_caching,
152
+ context_compression: context_compression,
153
+ token_budget_default: token_budget_default,
154
+ offline_mode: offline_mode
155
+ }
156
+ end
157
+
158
+ def brain_config
159
+ {
160
+ default_brain: default_brain,
161
+ auto_switching: auto_brain_switching
162
+ }
163
+ end
164
+
165
+ # Environment helpers
166
+ def development?
167
+ Rails.env.development? if defined?(Rails)
168
+ end
169
+
170
+ def production?
171
+ Rails.env.production? if defined?(Rails)
172
+ end
173
+
174
+ def test?
175
+ Rails.env.test? if defined?(Rails)
176
+ end
177
+
178
+ private
179
+
180
+ def valid_url?(url)
181
+ uri = URI.parse(url)
182
+ uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
183
+ rescue URI::InvalidURIError
184
+ false
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokipoki
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Hokipoki
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec
9
+ g.fixture_replacement :factory_bot
10
+ end
11
+
12
+ # Auto-load paths
13
+ config.autoload_paths += Dir[root.join('app', 'models', 'hokipoki')]
14
+ config.autoload_paths += Dir[root.join('app', 'services', 'hokipoki')]
15
+ config.autoload_paths += Dir[root.join('app', 'controllers', 'hokipoki')]
16
+ config.autoload_paths += Dir[root.join('app', 'jobs', 'hokipoki')]
17
+
18
+ # Assets
19
+ config.assets.precompile += %w[hokipoki/dashboard.css hokipoki/workshop.css]
20
+ config.assets.precompile += %w[hokipoki/dashboard.js hokipoki/parasite_optimizer_controller.js hokipoki/template_manager_controller.js]
21
+
22
+ initializer "hokipoki.initialize" do |app|
23
+ # Initialize Hokipoki configuration
24
+ Hokipoki.configure unless Hokipoki.configuration
25
+
26
+ # Validate configuration in non-test environments
27
+ unless Rails.env.test?
28
+ begin
29
+ Hokipoki.config.validate!
30
+ rescue Hokipoki::ConfigurationError => e
31
+ Rails.logger.error "❌ Hokipoki configuration error: #{e.message}"
32
+ raise e if Rails.env.production?
33
+ end
34
+ end
35
+
36
+ # Set up Redis connection
37
+ if Hokipoki.config.redis_url.present?
38
+ begin
39
+ $redis ||= Redis.new(Hokipoki.config.redis_config)
40
+ Rails.logger.info "🔗 Hokipoki: Redis connected at #{Hokipoki.config.redis_url}"
41
+ rescue => e
42
+ Rails.logger.error "❌ Hokipoki: Redis connection failed: #{e.message}"
43
+ end
44
+ end
45
+
46
+ # Initialize core services
47
+ Rails.application.config.after_initialize do
48
+ if Hokipoki.config.enable_parasites
49
+ begin
50
+ Hokipoki::Parasites::UniversalGenerator.instance
51
+ Rails.logger.info "🦠 Hokipoki: Universal Parasite Generator initialized"
52
+ rescue => e
53
+ Rails.logger.error "❌ Hokipoki: Parasite system initialization failed: #{e.message}"
54
+ end
55
+ end
56
+
57
+ if Hokipoki.config.enable_template_optimization
58
+ begin
59
+ Hokipoki::Intelligence::UnifiedOrchestrator.instance
60
+ Rails.logger.info "🧠 Hokipoki: Unified Intelligence Orchestrator initialized"
61
+ rescue => e
62
+ Rails.logger.error "❌ Hokipoki: Intelligence system initialization failed: #{e.message}"
63
+ end
64
+ end
65
+
66
+ if Hokipoki.config.enable_forge
67
+ begin
68
+ require 'hokipoki/forge/generator_forge'
69
+ Rails.logger.info "🏭 Hokipoki: Generator Forge initialized"
70
+ rescue LoadError => e
71
+ Rails.logger.warn "⚠️ Hokipoki: Forge components not available: #{e.message}"
72
+ rescue => e
73
+ Rails.logger.error "❌ Hokipoki: Forge initialization failed: #{e.message}"
74
+ end
75
+ end
76
+
77
+ # Initialize security audit service
78
+ if Hokipoki.config.audit_logging
79
+ begin
80
+ Hokipoki::Security::AuditService.instance
81
+ Rails.logger.info "🔒 Hokipoki: Security Audit Service initialized"
82
+ rescue => e
83
+ Rails.logger.error "❌ Hokipoki: Security service initialization failed: #{e.message}"
84
+ end
85
+ end
86
+
87
+ Rails.logger.info "✅ Hokipoki Engine initialized successfully (v#{Hokipoki::VERSION})"
88
+ end
89
+ end
90
+
91
+ # Add middleware for API authentication
92
+ initializer "hokipoki.middleware" do |app|
93
+ if Hokipoki.config.api_authentication
94
+ require 'hokipoki/security/api_authentication'
95
+ app.middleware.use Hokipoki::Security::ApiAuthentication
96
+ end
97
+ end
98
+
99
+ # Configure Sidekiq for background processing
100
+ initializer "hokipoki.sidekiq" do |app|
101
+ if defined?(Sidekiq) && Hokipoki.config.background_processing == :sidekiq
102
+ Sidekiq.configure_server do |config|
103
+ config.redis = Hokipoki.config.redis_config
104
+ end
105
+
106
+ Sidekiq.configure_client do |config|
107
+ config.redis = Hokipoki.config.redis_config
108
+ end
109
+
110
+ Rails.logger.info "⚙️ Hokipoki: Sidekiq configured for background processing"
111
+ end
112
+ end
113
+
114
+ # Add custom rake tasks
115
+ rake_tasks do
116
+ load 'tasks/hokipoki.rake'
117
+ end
118
+
119
+ # Generator paths
120
+ config.generators.templates.unshift File.expand_path('generators/templates', __dir__)
121
+ end
122
+ end