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.
- checksums.yaml +4 -4
- data/LICENSE +26 -0
- data/lib/generators/hive_mind/install_generator.rb +828 -0
- data/lib/hokipoki/claude/auto_loader.rb +162 -0
- data/lib/hokipoki/claude/connection_manager.rb +382 -0
- data/lib/hokipoki/claude/parasite.rb +333 -0
- data/lib/hokipoki/configuration.rb +187 -0
- data/lib/hokipoki/engine.rb +122 -0
- data/lib/hokipoki/feedback/ascii_banners.rb +108 -0
- data/lib/hokipoki/feedback/display_manager.rb +436 -0
- data/lib/hokipoki/intelligence/smart_retrieval_engine.rb +401 -0
- data/lib/hokipoki/intelligence/unified_orchestrator.rb +395 -0
- data/lib/hokipoki/license_validator.rb +296 -0
- data/lib/hokipoki/parasites/universal_generator.rb +662 -0
- data/lib/hokipoki/railtie.rb +34 -0
- data/lib/hokipoki/version.rb +1 -1
- data/lib/hokipoki.rb +177 -0
- metadata +77 -18
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hokipoki
|
|
4
|
+
module Claude
|
|
5
|
+
# Auto Loader - Automatically loads Claude integration when Claude CLI is detected
|
|
6
|
+
# Runs ACTIVATE_10X_CLAUDE.sh and establishes HiveMind connection
|
|
7
|
+
class AutoLoader
|
|
8
|
+
class << self
|
|
9
|
+
# Check if Claude CLI is running and auto-load if needed
|
|
10
|
+
def auto_load_if_claude_detected!
|
|
11
|
+
return unless claude_cli_detected?
|
|
12
|
+
return if already_loaded?
|
|
13
|
+
|
|
14
|
+
load_claude_integration!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Force load Claude integration
|
|
18
|
+
def force_load!
|
|
19
|
+
load_claude_integration!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Check if Claude CLI is currently running
|
|
23
|
+
def claude_cli_detected?
|
|
24
|
+
# Check for Claude CLI process
|
|
25
|
+
claude_processes = `ps aux | grep -i claude | grep -v grep`.strip
|
|
26
|
+
|
|
27
|
+
# Check for Claude CLI environment variables
|
|
28
|
+
claude_env_vars = ENV.keys.select { |key| key.include?('CLAUDE') }
|
|
29
|
+
|
|
30
|
+
# Check for Claude CLI in the current shell context
|
|
31
|
+
in_claude_session = ENV['CLAUDE_SESSION'] == 'true' ||
|
|
32
|
+
ENV['_'] =~ /claude/ ||
|
|
33
|
+
$PROGRAM_NAME =~ /claude/
|
|
34
|
+
|
|
35
|
+
claude_processes.present? || claude_env_vars.any? || in_claude_session
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check if already loaded to avoid double-loading
|
|
39
|
+
def already_loaded?
|
|
40
|
+
@loaded ||= false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def load_claude_integration!
|
|
46
|
+
return if @loaded
|
|
47
|
+
|
|
48
|
+
puts "\nš Claude CLI detected - Initializing HiveMind connection..."
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
# Step 1: Run ACTIVATE_10X_CLAUDE.sh if available
|
|
52
|
+
run_activation_script
|
|
53
|
+
|
|
54
|
+
# Step 2: Load Claude parasite
|
|
55
|
+
load_claude_parasite
|
|
56
|
+
|
|
57
|
+
# Step 3: Establish connection
|
|
58
|
+
establish_connection
|
|
59
|
+
|
|
60
|
+
# Step 4: Display success message
|
|
61
|
+
display_success_message
|
|
62
|
+
|
|
63
|
+
@loaded = true
|
|
64
|
+
|
|
65
|
+
rescue => e
|
|
66
|
+
display_error_message(e)
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def run_activation_script
|
|
72
|
+
script_paths = [
|
|
73
|
+
Rails.root.join('ACTIVATE_10X_CLAUDE.sh'),
|
|
74
|
+
Rails.root.join('bin', 'ACTIVATE_10X_CLAUDE.sh'),
|
|
75
|
+
'./ACTIVATE_10X_CLAUDE.sh'
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
script_path = script_paths.find { |path| File.exist?(path) }
|
|
79
|
+
|
|
80
|
+
if script_path
|
|
81
|
+
puts "š§ Running #{File.basename(script_path)}..."
|
|
82
|
+
|
|
83
|
+
# Ensure script is executable
|
|
84
|
+
File.chmod(0755, script_path) if File.exist?(script_path)
|
|
85
|
+
|
|
86
|
+
# Run script in background to avoid blocking
|
|
87
|
+
pid = spawn("cd #{Rails.root} && #{script_path} > /tmp/hokipoki_activation.log 2>&1")
|
|
88
|
+
|
|
89
|
+
# Wait a moment for script to start
|
|
90
|
+
sleep(1)
|
|
91
|
+
|
|
92
|
+
# Check if script is still running or completed successfully
|
|
93
|
+
begin
|
|
94
|
+
Process.waitpid(pid, Process::WNOHANG)
|
|
95
|
+
puts "ā Activation script initiated"
|
|
96
|
+
rescue Errno::ECHILD
|
|
97
|
+
puts "ā Activation script completed"
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
puts "ā¹ļø ACTIVATE_10X_CLAUDE.sh not found, skipping activation script"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def load_claude_parasite
|
|
105
|
+
puts "š¦ Loading Claude parasite..."
|
|
106
|
+
|
|
107
|
+
# Require Claude modules
|
|
108
|
+
require_relative 'connection_manager'
|
|
109
|
+
require_relative 'parasite'
|
|
110
|
+
|
|
111
|
+
# Auto-load the parasite
|
|
112
|
+
Claude::Parasite.auto_load!
|
|
113
|
+
|
|
114
|
+
puts "ā Claude parasite loaded"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def establish_connection
|
|
118
|
+
puts "š Establishing HiveMind connection..."
|
|
119
|
+
|
|
120
|
+
# Get connection manager and establish connection
|
|
121
|
+
connection_manager = Claude::ConnectionManager.instance
|
|
122
|
+
connection_manager.establish_connection!
|
|
123
|
+
|
|
124
|
+
puts "ā HiveMind connection established"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def display_success_message
|
|
128
|
+
pastel = Pastel.new
|
|
129
|
+
|
|
130
|
+
puts pastel.green.bold("\nš Claude ā HiveMind Integration Active!")
|
|
131
|
+
puts pastel.cyan(" š§ Vector intelligence: Online")
|
|
132
|
+
puts pastel.cyan(" š¦ Smart context injection: Enabled")
|
|
133
|
+
puts pastel.cyan(" ā” Auto-enhancement: Ready")
|
|
134
|
+
|
|
135
|
+
puts pastel.yellow("\nš” Your Claude responses are now supercharged!")
|
|
136
|
+
puts pastel.dim(" Every query will be enhanced with relevant project context.\n")
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def display_error_message(error)
|
|
140
|
+
pastel = Pastel.new
|
|
141
|
+
|
|
142
|
+
puts pastel.red.bold("\nā Claude Integration Failed")
|
|
143
|
+
puts pastel.red(" Error: #{error.message}")
|
|
144
|
+
puts pastel.yellow("\nš§ Quick Fix:")
|
|
145
|
+
puts pastel.dim(" Run: rails g hive_mind:install --claude")
|
|
146
|
+
puts ""
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Auto-load when Rails starts if Claude is detected
|
|
154
|
+
if defined?(Rails) && Rails.env.development?
|
|
155
|
+
Rails.application.config.after_initialize do
|
|
156
|
+
# Small delay to ensure everything is loaded
|
|
157
|
+
Thread.new do
|
|
158
|
+
sleep(2) # Give Rails time to fully initialize
|
|
159
|
+
Hokipoki::Claude::AutoLoader.auto_load_if_claude_detected!
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pastel'
|
|
4
|
+
require 'tty-spinner'
|
|
5
|
+
|
|
6
|
+
module Hokipoki
|
|
7
|
+
module Claude
|
|
8
|
+
# Claude Connection Manager
|
|
9
|
+
# Handles Claude CLI integration, vector DB connection, and user feedback
|
|
10
|
+
class ConnectionManager
|
|
11
|
+
include Singleton
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@pastel = Pastel.new
|
|
15
|
+
@logger = Rails.logger
|
|
16
|
+
@connected = false
|
|
17
|
+
@connection_time = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Main connection method called when Claude starts
|
|
21
|
+
def establish_connection!
|
|
22
|
+
return if @connected
|
|
23
|
+
|
|
24
|
+
display_connection_banner
|
|
25
|
+
|
|
26
|
+
spinner = TTY::Spinner.new("[:spinner] #{@pastel.yellow('Connecting to HiveMind vector database...')}", format: :dots)
|
|
27
|
+
spinner.auto_spin
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
# Step 1: Check vector database connection
|
|
31
|
+
check_vector_database!
|
|
32
|
+
|
|
33
|
+
# Step 2: Run activation script
|
|
34
|
+
run_activation_script!
|
|
35
|
+
|
|
36
|
+
# Step 3: Verify parasite activation
|
|
37
|
+
verify_claude_parasite!
|
|
38
|
+
|
|
39
|
+
# Step 4: Test vector retrieval
|
|
40
|
+
test_vector_retrieval!
|
|
41
|
+
|
|
42
|
+
spinner.stop(@pastel.green('ā'))
|
|
43
|
+
display_success_message
|
|
44
|
+
|
|
45
|
+
@connected = true
|
|
46
|
+
@connection_time = Time.current
|
|
47
|
+
|
|
48
|
+
# Send connection analytics
|
|
49
|
+
track_connection_event('connected')
|
|
50
|
+
|
|
51
|
+
rescue => e
|
|
52
|
+
spinner.stop(@pastel.red('ā'))
|
|
53
|
+
display_error_message(e)
|
|
54
|
+
track_connection_event('failed', error: e.message)
|
|
55
|
+
raise e
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check if connected to HiveMind
|
|
60
|
+
def connected?
|
|
61
|
+
@connected && vector_database_accessible?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get connection status
|
|
65
|
+
def connection_status
|
|
66
|
+
{
|
|
67
|
+
connected: connected?,
|
|
68
|
+
connection_time: @connection_time,
|
|
69
|
+
vector_db_status: vector_database_status,
|
|
70
|
+
parasite_status: claude_parasite_status,
|
|
71
|
+
last_retrieval: last_vector_retrieval_time,
|
|
72
|
+
system_health: system_health_check
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Display connection info in terminal
|
|
77
|
+
def display_connection_info
|
|
78
|
+
if connected?
|
|
79
|
+
puts @pastel.green.bold("\nš§ HiveMind Status: CONNECTED")
|
|
80
|
+
puts @pastel.cyan(" š Vector Database: #{vector_database_status[:status]}")
|
|
81
|
+
puts @pastel.cyan(" š¦ Claude Parasite: #{claude_parasite_status[:status]}")
|
|
82
|
+
puts @pastel.cyan(" ā” Last Retrieval: #{format_time(last_vector_retrieval_time)}")
|
|
83
|
+
puts @pastel.cyan(" š Connected Since: #{format_time(@connection_time)}")
|
|
84
|
+
else
|
|
85
|
+
puts @pastel.red.bold("\nš« HiveMind Status: DISCONNECTED")
|
|
86
|
+
puts @pastel.yellow(" Run: rails g hive_mind:install --claude")
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Disconnect from HiveMind
|
|
91
|
+
def disconnect!
|
|
92
|
+
@connected = false
|
|
93
|
+
@connection_time = nil
|
|
94
|
+
track_connection_event('disconnected')
|
|
95
|
+
puts @pastel.yellow("\nš HiveMind disconnected")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Test vector retrieval with feedback
|
|
99
|
+
def test_vector_retrieval(query = "test connection")
|
|
100
|
+
return false unless connected?
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
result = Hokipoki.retrieve_facts(query, token_budget: 500)
|
|
104
|
+
|
|
105
|
+
if result.present?
|
|
106
|
+
puts @pastel.green("ā Vector retrieval test successful")
|
|
107
|
+
puts @pastel.dim(" Query: #{query}")
|
|
108
|
+
puts @pastel.dim(" Result: #{result[0..100]}...")
|
|
109
|
+
update_last_retrieval_time
|
|
110
|
+
true
|
|
111
|
+
else
|
|
112
|
+
puts @pastel.yellow("ā Vector retrieval returned empty results")
|
|
113
|
+
false
|
|
114
|
+
end
|
|
115
|
+
rescue => e
|
|
116
|
+
puts @pastel.red("ā Vector retrieval test failed: #{e.message}")
|
|
117
|
+
false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def display_connection_banner
|
|
124
|
+
puts "\n#{@pastel.cyan.bold('š HokiPoki HiveMind Connection')}"
|
|
125
|
+
puts @pastel.dim(" Initializing Claude ā Vector Database integration...")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def check_vector_database!
|
|
129
|
+
# Check PostgreSQL connection
|
|
130
|
+
unless ActiveRecord::Base.connection.active?
|
|
131
|
+
raise ConnectionError, "PostgreSQL database not accessible"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Check pgvector extension
|
|
135
|
+
unless pgvector_available?
|
|
136
|
+
raise ConnectionError, "pgvector extension not installed"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Check if documents table exists
|
|
140
|
+
unless ActiveRecord::Base.connection.table_exists?('documents')
|
|
141
|
+
raise ConnectionError, "Vector database not initialized. Run: rails db:migrate"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
puts @pastel.green(" ā Vector database accessible")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def run_activation_script!
|
|
148
|
+
script_path = Rails.root.join('ACTIVATE_10X_CLAUDE.sh')
|
|
149
|
+
|
|
150
|
+
if File.exist?(script_path)
|
|
151
|
+
puts @pastel.cyan(" š§ Running ACTIVATE_10X_CLAUDE.sh...")
|
|
152
|
+
|
|
153
|
+
# Make sure script is executable
|
|
154
|
+
File.chmod(0755, script_path)
|
|
155
|
+
|
|
156
|
+
# Run the script and capture output
|
|
157
|
+
result = system("cd #{Rails.root} && ./ACTIVATE_10X_CLAUDE.sh > /dev/null 2>&1")
|
|
158
|
+
|
|
159
|
+
if result
|
|
160
|
+
puts @pastel.green(" ā Activation script completed successfully")
|
|
161
|
+
else
|
|
162
|
+
puts @pastel.yellow(" ā Activation script completed with warnings")
|
|
163
|
+
end
|
|
164
|
+
else
|
|
165
|
+
puts @pastel.yellow(" ā ACTIVATE_10X_CLAUDE.sh not found, skipping...")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def verify_claude_parasite!
|
|
170
|
+
# Check if Claude parasite is available
|
|
171
|
+
if claude_parasite_available?
|
|
172
|
+
puts @pastel.green(" ā Claude parasite loaded and ready")
|
|
173
|
+
else
|
|
174
|
+
puts @pastel.yellow(" ā Claude parasite not found, generating...")
|
|
175
|
+
generate_claude_parasite!
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def generate_claude_parasite!
|
|
180
|
+
parasite_result = Hokipoki.generate_parasite(
|
|
181
|
+
tool: 'claude_cli',
|
|
182
|
+
model: 'claude',
|
|
183
|
+
context_type: 'programming',
|
|
184
|
+
injection_style: 'natural',
|
|
185
|
+
behavioral_optimization: true,
|
|
186
|
+
auto_activate: true
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if parasite_result[:success]
|
|
190
|
+
puts @pastel.green(" ā Claude parasite generated successfully")
|
|
191
|
+
else
|
|
192
|
+
raise ConnectionError, "Failed to generate Claude parasite: #{parasite_result[:error]}"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def test_vector_retrieval!
|
|
197
|
+
test_query = "hokipoki vector database connection test"
|
|
198
|
+
result = Hokipoki.retrieve_facts(test_query, token_budget: 300)
|
|
199
|
+
|
|
200
|
+
if result.present?
|
|
201
|
+
puts @pastel.green(" ā Vector retrieval operational")
|
|
202
|
+
update_last_retrieval_time
|
|
203
|
+
else
|
|
204
|
+
puts @pastel.yellow(" ā Vector retrieval test returned empty (database may be empty)")
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def display_success_message
|
|
209
|
+
puts "\n#{@pastel.green.bold('š HiveMind Connection Established!')}"
|
|
210
|
+
puts @pastel.cyan(" š§ Vector database: Ready")
|
|
211
|
+
puts @pastel.cyan(" š¦ Claude parasite: Active")
|
|
212
|
+
puts @pastel.cyan(" ā” Intelligence engine: Online")
|
|
213
|
+
puts @pastel.cyan(" š Claude CLI: Connected")
|
|
214
|
+
|
|
215
|
+
puts "\n#{@pastel.yellow.bold('š” Quick Commands:')}"
|
|
216
|
+
puts @pastel.dim(" Test retrieval: Hokipoki.retrieve_facts('your query')")
|
|
217
|
+
puts @pastel.dim(" System status: Hokipoki.system_status")
|
|
218
|
+
puts @pastel.dim(" Connection info: display_connection_info")
|
|
219
|
+
|
|
220
|
+
puts "\n#{@pastel.magenta('Happy coding with enhanced AI intelligence! š')}\n"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def display_error_message(error)
|
|
224
|
+
puts "\n#{@pastel.red.bold('ā HiveMind Connection Failed')}"
|
|
225
|
+
puts @pastel.red(" Error: #{error.message}")
|
|
226
|
+
|
|
227
|
+
puts "\n#{@pastel.yellow.bold('š§ Troubleshooting:')}"
|
|
228
|
+
puts @pastel.dim(" 1. Ensure PostgreSQL is running")
|
|
229
|
+
puts @pastel.dim(" 2. Run: rails db:migrate")
|
|
230
|
+
puts @pastel.dim(" 3. Check: rails g hive_mind:install --claude")
|
|
231
|
+
puts @pastel.dim(" 4. Verify license: echo $HOKIPOKI_LICENSE_KEY")
|
|
232
|
+
puts ""
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def pgvector_available?
|
|
236
|
+
ActiveRecord::Base.connection.execute(
|
|
237
|
+
"SELECT 1 FROM pg_extension WHERE extname = 'vector'"
|
|
238
|
+
).any?
|
|
239
|
+
rescue
|
|
240
|
+
false
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def claude_parasite_available?
|
|
244
|
+
# Check if Claude parasite exists in database
|
|
245
|
+
return false unless defined?(Hokipoki::CustomParasite)
|
|
246
|
+
|
|
247
|
+
Hokipoki::CustomParasite.for_tool_and_model('claude_cli', 'claude').active.any?
|
|
248
|
+
rescue
|
|
249
|
+
false
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def vector_database_accessible?
|
|
253
|
+
return false unless defined?(ActiveRecord::Base)
|
|
254
|
+
|
|
255
|
+
ActiveRecord::Base.connection.active? &&
|
|
256
|
+
ActiveRecord::Base.connection.table_exists?('documents')
|
|
257
|
+
rescue
|
|
258
|
+
false
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def vector_database_status
|
|
262
|
+
if vector_database_accessible?
|
|
263
|
+
document_count = Document.count rescue 0
|
|
264
|
+
{
|
|
265
|
+
status: 'online',
|
|
266
|
+
document_count: document_count,
|
|
267
|
+
last_check: Time.current
|
|
268
|
+
}
|
|
269
|
+
else
|
|
270
|
+
{
|
|
271
|
+
status: 'offline',
|
|
272
|
+
document_count: 0,
|
|
273
|
+
last_check: Time.current
|
|
274
|
+
}
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def claude_parasite_status
|
|
279
|
+
if claude_parasite_available?
|
|
280
|
+
parasite = Hokipoki::CustomParasite.for_tool_and_model('claude_cli', 'claude').active.first
|
|
281
|
+
{
|
|
282
|
+
status: 'active',
|
|
283
|
+
name: parasite&.name,
|
|
284
|
+
last_used: parasite&.last_used_at,
|
|
285
|
+
success_rate: parasite&.success_rate
|
|
286
|
+
}
|
|
287
|
+
else
|
|
288
|
+
{
|
|
289
|
+
status: 'inactive',
|
|
290
|
+
name: nil,
|
|
291
|
+
last_used: nil,
|
|
292
|
+
success_rate: 0
|
|
293
|
+
}
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def last_vector_retrieval_time
|
|
298
|
+
Rails.cache.read('hokipoki_last_vector_retrieval') || 'never'
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def update_last_retrieval_time
|
|
302
|
+
Rails.cache.write('hokipoki_last_vector_retrieval', Time.current, expires_in: 24.hours)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def system_health_check
|
|
306
|
+
{
|
|
307
|
+
database: vector_database_accessible? ? 'healthy' : 'unhealthy',
|
|
308
|
+
redis: redis_accessible? ? 'healthy' : 'unhealthy',
|
|
309
|
+
license: license_valid? ? 'valid' : 'invalid',
|
|
310
|
+
overall: overall_health_status
|
|
311
|
+
}
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def redis_accessible?
|
|
315
|
+
return false unless defined?(Redis)
|
|
316
|
+
|
|
317
|
+
Redis.new(Hokipoki.config.redis_config).ping == 'PONG'
|
|
318
|
+
rescue
|
|
319
|
+
false
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def license_valid?
|
|
323
|
+
Hokipoki::LicenseValidator.valid_license?(
|
|
324
|
+
ENV['HOKIPOKI_LICENSE_KEY']
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def overall_health_status
|
|
329
|
+
health = system_health_check
|
|
330
|
+
if health[:database] == 'healthy' && health[:license] == 'valid'
|
|
331
|
+
'healthy'
|
|
332
|
+
else
|
|
333
|
+
'degraded'
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def track_connection_event(event_type, metadata = {})
|
|
338
|
+
return unless Rails.env.production?
|
|
339
|
+
|
|
340
|
+
event_data = {
|
|
341
|
+
event: "claude_connection_#{event_type}",
|
|
342
|
+
timestamp: Time.current.iso8601,
|
|
343
|
+
user_id: ENV['USER'] || 'unknown',
|
|
344
|
+
hostname: Socket.gethostname,
|
|
345
|
+
rails_env: Rails.env,
|
|
346
|
+
gem_version: Hokipoki::VERSION,
|
|
347
|
+
metadata: metadata
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# Log locally
|
|
351
|
+
Rails.logger.info "š Claude Connection Event: #{event_data.to_json}"
|
|
352
|
+
|
|
353
|
+
# Send to analytics (non-blocking)
|
|
354
|
+
Thread.new { send_analytics_event(event_data) }
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def send_analytics_event(event_data)
|
|
358
|
+
return unless Hokipoki.config.audit_logging
|
|
359
|
+
|
|
360
|
+
begin
|
|
361
|
+
# Send to your analytics endpoint
|
|
362
|
+
HTTP.timeout(3).post(
|
|
363
|
+
'https://analytics.hokipoki.ai/events',
|
|
364
|
+
json: event_data,
|
|
365
|
+
headers: { 'Content-Type': 'application/json' }
|
|
366
|
+
)
|
|
367
|
+
rescue
|
|
368
|
+
# Silently fail - analytics should never break the application
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def format_time(time)
|
|
373
|
+
return 'never' unless time
|
|
374
|
+
return time if time.is_a?(String)
|
|
375
|
+
|
|
376
|
+
time.strftime('%H:%M:%S')
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
class ConnectionError < Hokipoki::Error; end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|