htm 0.0.15 → 0.0.18
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/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +1 -1
- data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +4 -4
- data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +1 -1
- data/.envrc +12 -24
- data/.irbrc +7 -7
- data/.tbls.yml +2 -2
- data/CHANGELOG.md +138 -0
- data/README.md +97 -1592
- data/Rakefile +8 -3
- data/SETUP.md +12 -12
- data/bin/htm_mcp +27 -0
- data/db/seed_data/README.md +2 -2
- data/db/seeds.rb +2 -2
- data/docs/api/database.md +37 -37
- data/docs/api/htm.md +1 -1
- data/docs/api/yard/HTM/ActiveRecordConfig.md +2 -2
- data/docs/api/yard/HTM/Configuration.md +26 -15
- data/docs/api/yard/HTM/Database.md +7 -8
- data/docs/api/yard/HTM/JobAdapter.md +1 -1
- data/docs/api/yard/HTM/Railtie.md +2 -2
- data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
- data/docs/architecture/adrs/011-pgai-integration.md +4 -4
- data/docs/database_rake_tasks.md +5 -5
- data/docs/development/rake-tasks.md +11 -11
- data/docs/development/setup.md +21 -21
- data/docs/development/testing.md +1 -1
- data/docs/getting-started/installation.md +51 -31
- data/docs/getting-started/quick-start.md +12 -12
- data/docs/guides/getting-started.md +2 -2
- data/docs/guides/long-term-memory.md +1 -1
- data/docs/guides/mcp-server.md +464 -29
- data/docs/guides/robot-groups.md +8 -8
- data/docs/index.md +4 -4
- data/docs/multi_framework_support.md +10 -10
- data/docs/setup_local_database.md +19 -19
- data/docs/using_rake_tasks_in_your_app.md +14 -14
- data/examples/README.md +50 -6
- data/examples/basic_usage.rb +31 -21
- data/examples/cli_app/README.md +8 -8
- data/examples/cli_app/htm_cli.rb +5 -5
- data/examples/config_file_example/README.md +256 -0
- data/examples/config_file_example/config/htm.local.yml +34 -0
- data/examples/config_file_example/custom_config.yml +22 -0
- data/examples/config_file_example/show_config.rb +125 -0
- data/examples/custom_llm_configuration.rb +7 -7
- data/examples/example_app/Rakefile +2 -2
- data/examples/example_app/app.rb +8 -8
- data/examples/file_loader_usage.rb +9 -9
- data/examples/mcp_client.rb +7 -7
- data/examples/rails_app/.gitignore +2 -0
- data/examples/rails_app/Gemfile +22 -0
- data/examples/rails_app/Gemfile.lock +430 -0
- data/examples/rails_app/Procfile.dev +1 -0
- data/examples/rails_app/README.md +98 -0
- data/examples/rails_app/Rakefile +5 -0
- data/examples/rails_app/app/assets/stylesheets/application.css +83 -0
- data/examples/rails_app/app/assets/stylesheets/inter-font.css +6 -0
- data/examples/rails_app/app/controllers/application_controller.rb +19 -0
- data/examples/rails_app/app/controllers/dashboard_controller.rb +27 -0
- data/examples/rails_app/app/controllers/files_controller.rb +205 -0
- data/examples/rails_app/app/controllers/memories_controller.rb +102 -0
- data/examples/rails_app/app/controllers/robots_controller.rb +44 -0
- data/examples/rails_app/app/controllers/search_controller.rb +46 -0
- data/examples/rails_app/app/controllers/tags_controller.rb +30 -0
- data/examples/rails_app/app/javascript/application.js +4 -0
- data/examples/rails_app/app/javascript/controllers/application.js +9 -0
- data/examples/rails_app/app/javascript/controllers/index.js +6 -0
- data/examples/rails_app/app/views/dashboard/index.html.erb +123 -0
- data/examples/rails_app/app/views/files/index.html.erb +108 -0
- data/examples/rails_app/app/views/files/new.html.erb +321 -0
- data/examples/rails_app/app/views/files/show.html.erb +130 -0
- data/examples/rails_app/app/views/layouts/application.html.erb +124 -0
- data/examples/rails_app/app/views/memories/_memory_card.html.erb +51 -0
- data/examples/rails_app/app/views/memories/deleted.html.erb +62 -0
- data/examples/rails_app/app/views/memories/edit.html.erb +35 -0
- data/examples/rails_app/app/views/memories/index.html.erb +81 -0
- data/examples/rails_app/app/views/memories/new.html.erb +71 -0
- data/examples/rails_app/app/views/memories/show.html.erb +126 -0
- data/examples/rails_app/app/views/robots/index.html.erb +106 -0
- data/examples/rails_app/app/views/robots/new.html.erb +36 -0
- data/examples/rails_app/app/views/robots/show.html.erb +79 -0
- data/examples/rails_app/app/views/search/index.html.erb +184 -0
- data/examples/rails_app/app/views/shared/_navbar.html.erb +52 -0
- data/examples/rails_app/app/views/shared/_stat_card.html.erb +52 -0
- data/examples/rails_app/app/views/tags/index.html.erb +131 -0
- data/examples/rails_app/app/views/tags/show.html.erb +67 -0
- data/examples/rails_app/bin/dev +8 -0
- data/examples/rails_app/bin/rails +4 -0
- data/examples/rails_app/bin/rake +4 -0
- data/examples/rails_app/config/application.rb +33 -0
- data/examples/rails_app/config/boot.rb +5 -0
- data/examples/rails_app/config/database.yml +15 -0
- data/examples/rails_app/config/environment.rb +5 -0
- data/examples/rails_app/config/importmap.rb +7 -0
- data/examples/rails_app/config/routes.rb +38 -0
- data/examples/rails_app/config/tailwind.config.js +35 -0
- data/examples/rails_app/config.ru +5 -0
- data/examples/rails_app/log/.keep +0 -0
- data/examples/rails_app/tmp/local_secret.txt +1 -0
- data/examples/robot_groups/multi_process.rb +5 -5
- data/examples/robot_groups/robot_worker.rb +5 -5
- data/examples/robot_groups/same_process.rb +9 -9
- data/examples/sinatra_app/app.rb +1 -1
- data/examples/timeframe_demo.rb +1 -1
- data/lib/htm/active_record_config.rb +12 -28
- data/lib/htm/circuit_breaker.rb +0 -2
- data/lib/htm/config/defaults.yml +246 -0
- data/lib/htm/config.rb +888 -0
- data/lib/htm/database.rb +26 -33
- data/lib/htm/embedding_service.rb +0 -4
- data/lib/htm/integrations/sinatra.rb +3 -7
- data/lib/htm/job_adapter.rb +1 -15
- data/lib/htm/jobs/generate_embedding_job.rb +1 -7
- data/lib/htm/jobs/generate_propositions_job.rb +2 -12
- data/lib/htm/jobs/generate_tags_job.rb +1 -8
- data/lib/htm/loaders/defaults_loader.rb +143 -0
- data/lib/htm/loaders/xdg_config_loader.rb +116 -0
- data/lib/htm/mcp/cli.rb +475 -0
- data/lib/htm/mcp/group_tools.rb +476 -0
- data/lib/htm/mcp/resources.rb +89 -0
- data/lib/htm/mcp/server.rb +98 -0
- data/lib/htm/mcp/tools.rb +488 -0
- data/lib/htm/models/file_source.rb +5 -3
- data/lib/htm/proposition_service.rb +2 -12
- data/lib/htm/railtie.rb +3 -8
- data/lib/htm/tag_service.rb +1 -8
- data/lib/htm/tasks.rb +7 -4
- data/lib/htm/version.rb +1 -1
- data/lib/htm.rb +124 -5
- data/lib/tasks/htm.rake +6 -9
- metadata +81 -6
- data/bin/htm_mcp.rb +0 -621
- data/config/database.yml +0 -74
- data/lib/htm/configuration.rb +0 -766
data/lib/htm/mcp/cli.rb
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class HTM
|
|
4
|
+
module MCP
|
|
5
|
+
# CLI commands for htm_mcp executable
|
|
6
|
+
module CLI
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def print_help
|
|
10
|
+
puts <<~HELP
|
|
11
|
+
HTM MCP Server v#{HTM::VERSION} - Memory management for AI assistants
|
|
12
|
+
|
|
13
|
+
USAGE:
|
|
14
|
+
htm_mcp [COMMAND]
|
|
15
|
+
|
|
16
|
+
COMMANDS:
|
|
17
|
+
server Start the MCP server (default if no command given)
|
|
18
|
+
stdio Alias for server (for MCP client compatibility)
|
|
19
|
+
setup Initialize the database schema
|
|
20
|
+
init Alias for setup
|
|
21
|
+
verify Verify database connection and extensions
|
|
22
|
+
stats Show memory statistics
|
|
23
|
+
config Output default configuration to STDOUT
|
|
24
|
+
version Show HTM version
|
|
25
|
+
help Show this help message
|
|
26
|
+
|
|
27
|
+
ENVIRONMENT VARIABLES:
|
|
28
|
+
|
|
29
|
+
Note: Nested config uses double underscores (e.g., HTM_EMBEDDING__PROVIDER)
|
|
30
|
+
|
|
31
|
+
Environment:
|
|
32
|
+
HTM_ENV Environment name: development, test, production
|
|
33
|
+
(priority: HTM_ENV > RAILS_ENV > RACK_ENV > 'development')
|
|
34
|
+
|
|
35
|
+
Database:
|
|
36
|
+
HTM_DATABASE__URL PostgreSQL connection URL (preferred)
|
|
37
|
+
Example: postgresql://user:pass@localhost:5432/htm_development
|
|
38
|
+
HTM_DATABASE__HOST Database host (default: localhost)
|
|
39
|
+
HTM_DATABASE__PORT Database port (default: 5432)
|
|
40
|
+
HTM_DATABASE__NAME Database name
|
|
41
|
+
HTM_DATABASE__USER Database username
|
|
42
|
+
HTM_DATABASE__PASSWORD Database password
|
|
43
|
+
HTM_DATABASE__SSLMODE SSL mode (default: prefer)
|
|
44
|
+
HTM_DATABASE__POOL_SIZE Connection pool size (default: 10)
|
|
45
|
+
|
|
46
|
+
Embedding:
|
|
47
|
+
HTM_EMBEDDING__PROVIDER Provider (default: ollama)
|
|
48
|
+
HTM_EMBEDDING__MODEL Model (default: nomic-embed-text:latest)
|
|
49
|
+
HTM_EMBEDDING__DIMENSIONS Dimensions (default: 768)
|
|
50
|
+
HTM_EMBEDDING__TIMEOUT Timeout seconds (default: 120)
|
|
51
|
+
HTM_EMBEDDING__MAX_DIMENSION Max dimensions (default: 2000)
|
|
52
|
+
|
|
53
|
+
Tag Extraction:
|
|
54
|
+
HTM_TAG__PROVIDER Provider (default: ollama)
|
|
55
|
+
HTM_TAG__MODEL Model (default: gemma3:latest)
|
|
56
|
+
HTM_TAG__TIMEOUT Timeout seconds (default: 180)
|
|
57
|
+
HTM_TAG__MAX_DEPTH Max hierarchy depth (default: 4)
|
|
58
|
+
|
|
59
|
+
Proposition Extraction:
|
|
60
|
+
HTM_PROPOSITION__PROVIDER Provider (default: ollama)
|
|
61
|
+
HTM_PROPOSITION__MODEL Model (default: gemma3:latest)
|
|
62
|
+
HTM_PROPOSITION__TIMEOUT Timeout seconds (default: 180)
|
|
63
|
+
HTM_PROPOSITION__ENABLED Enable extraction (default: false)
|
|
64
|
+
|
|
65
|
+
Chunking:
|
|
66
|
+
HTM_CHUNKING__SIZE Max chars per chunk (default: 1024)
|
|
67
|
+
HTM_CHUNKING__OVERLAP Chunk overlap chars (default: 64)
|
|
68
|
+
|
|
69
|
+
Job Backend:
|
|
70
|
+
HTM_JOB__BACKEND Backend: inline, thread, active_job, sidekiq
|
|
71
|
+
|
|
72
|
+
Provider API Keys:
|
|
73
|
+
HTM_PROVIDERS__OLLAMA__URL Ollama URL (default: http://localhost:11434)
|
|
74
|
+
HTM_PROVIDERS__OPENAI__API_KEY OpenAI API key
|
|
75
|
+
HTM_PROVIDERS__ANTHROPIC__API_KEY Anthropic API key
|
|
76
|
+
HTM_PROVIDERS__GEMINI__API_KEY Google Gemini API key
|
|
77
|
+
HTM_PROVIDERS__AZURE__API_KEY Azure OpenAI API key
|
|
78
|
+
HTM_PROVIDERS__AZURE__ENDPOINT Azure OpenAI endpoint
|
|
79
|
+
|
|
80
|
+
Other:
|
|
81
|
+
HTM_LOG_LEVEL Log level (default: info)
|
|
82
|
+
HTM_CONNECTION_TIMEOUT Connection timeout seconds (default: 30)
|
|
83
|
+
HTM_TELEMETRY_ENABLED Enable OpenTelemetry (default: false)
|
|
84
|
+
|
|
85
|
+
OPTIONS:
|
|
86
|
+
-c, --config [PATH] Without PATH: output default config to STDOUT
|
|
87
|
+
With PATH: load config from YAML file
|
|
88
|
+
|
|
89
|
+
EXAMPLES:
|
|
90
|
+
# Generate a config file template
|
|
91
|
+
htm_mcp --config > my_config.yml
|
|
92
|
+
|
|
93
|
+
# Start server with custom config
|
|
94
|
+
htm_mcp --config my_config.yml
|
|
95
|
+
|
|
96
|
+
# First-time setup
|
|
97
|
+
export HTM_DATABASE__URL="postgresql://postgres@localhost:5432/htm"
|
|
98
|
+
htm_mcp setup
|
|
99
|
+
|
|
100
|
+
# Verify connection
|
|
101
|
+
htm_mcp verify
|
|
102
|
+
|
|
103
|
+
# Use test database
|
|
104
|
+
HTM_ENV=test htm_mcp setup
|
|
105
|
+
HTM_ENV=test htm_mcp stats
|
|
106
|
+
|
|
107
|
+
# Start MCP server (for Claude Desktop)
|
|
108
|
+
htm_mcp
|
|
109
|
+
|
|
110
|
+
CLAUDE DESKTOP CONFIGURATION:
|
|
111
|
+
Add to ~/.config/claude/claude_desktop_config.json:
|
|
112
|
+
|
|
113
|
+
{
|
|
114
|
+
"mcpServers": {
|
|
115
|
+
"htm-memory": {
|
|
116
|
+
"command": "/path/to/htm_mcp",
|
|
117
|
+
"env": {
|
|
118
|
+
"HTM_DATABASE__URL": "postgresql://postgres@localhost:5432/htm_development"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
HELP
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def check_database_config!
|
|
127
|
+
unless ENV['HTM_DATABASE__URL'] || ENV['HTM_DATABASE__NAME']
|
|
128
|
+
warn "Error: Database not configured."
|
|
129
|
+
warn "Set HTM_DATABASE__URL or HTM_DATABASE__NAME environment variable."
|
|
130
|
+
warn "Run 'htm_mcp help' for details."
|
|
131
|
+
exit 1
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def print_error_suggestion(error_message)
|
|
136
|
+
msg = error_message.to_s.downcase
|
|
137
|
+
|
|
138
|
+
warn ""
|
|
139
|
+
if msg.include?("does not exist")
|
|
140
|
+
warn "Suggestion: The database does not exist. Create it with:"
|
|
141
|
+
warn " createdb #{extract_dbname(ENV['HTM_DATABASE__URL'] || ENV['HTM_DATABASE__NAME'])}"
|
|
142
|
+
warn "Then initialize the schema with:"
|
|
143
|
+
warn " htm_mcp setup"
|
|
144
|
+
elsif msg.include?("password authentication failed") || msg.include?("no password supplied")
|
|
145
|
+
warn "Suggestion: Check your database credentials."
|
|
146
|
+
warn "Verify HTM_DATABASE__URL has correct username and password:"
|
|
147
|
+
warn " postgresql://USER:PASSWORD@localhost:5432/DATABASE"
|
|
148
|
+
elsif msg.include?("connection refused") || msg.include?("could not connect")
|
|
149
|
+
warn "Suggestion: PostgreSQL server is not running or not accepting connections."
|
|
150
|
+
warn "Start PostgreSQL with:"
|
|
151
|
+
warn " brew services start postgresql@17 # macOS with Homebrew"
|
|
152
|
+
warn " sudo systemctl start postgresql # Linux"
|
|
153
|
+
elsif msg.include?("role") && msg.include?("does not exist")
|
|
154
|
+
warn "Suggestion: The database user does not exist. Create it with:"
|
|
155
|
+
warn " createuser -s YOUR_USERNAME"
|
|
156
|
+
elsif msg.include?("permission denied")
|
|
157
|
+
warn "Suggestion: The user lacks permission to access this database."
|
|
158
|
+
warn "Grant access or use a different user with appropriate privileges."
|
|
159
|
+
elsif msg.include?("timeout") || msg.include?("timed out")
|
|
160
|
+
warn "Suggestion: Connection timed out. Check:"
|
|
161
|
+
warn " - PostgreSQL is running"
|
|
162
|
+
warn " - Firewall allows connections on port 5432"
|
|
163
|
+
warn " - Host address is correct"
|
|
164
|
+
elsif msg.include?("extension") && msg.include?("vector")
|
|
165
|
+
warn "Suggestion: pgvector extension is not installed. Install it with:"
|
|
166
|
+
warn " brew install pgvector # macOS"
|
|
167
|
+
warn "Then enable it in your database:"
|
|
168
|
+
warn " psql -d DATABASE -c 'CREATE EXTENSION vector;'"
|
|
169
|
+
else
|
|
170
|
+
warn "Suggestion: Run 'htm_mcp help' for configuration details."
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def extract_dbname(url_or_name)
|
|
175
|
+
return url_or_name unless url_or_name&.include?("://")
|
|
176
|
+
|
|
177
|
+
# Extract database name from URL like postgresql://user@host:port/dbname
|
|
178
|
+
if url_or_name =~ %r{/([^/?]+)(?:\?|$)}
|
|
179
|
+
$1
|
|
180
|
+
else
|
|
181
|
+
"htm_development"
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def run_setup
|
|
186
|
+
puts "HTM Database Setup"
|
|
187
|
+
puts "=================="
|
|
188
|
+
puts
|
|
189
|
+
|
|
190
|
+
check_database_config!
|
|
191
|
+
|
|
192
|
+
begin
|
|
193
|
+
HTM::Database.setup
|
|
194
|
+
puts
|
|
195
|
+
puts "Database initialized successfully!"
|
|
196
|
+
puts "You can now start the MCP server with: htm_mcp"
|
|
197
|
+
rescue => e
|
|
198
|
+
warn "Setup failed: #{e.message}"
|
|
199
|
+
print_error_suggestion(e.message)
|
|
200
|
+
warn e.backtrace.first(5).join("\n") if ENV['DEBUG']
|
|
201
|
+
exit 1
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def run_verify
|
|
206
|
+
puts "HTM Database Verification"
|
|
207
|
+
puts "========================="
|
|
208
|
+
puts
|
|
209
|
+
|
|
210
|
+
check_database_config!
|
|
211
|
+
|
|
212
|
+
begin
|
|
213
|
+
HTM::Database.info
|
|
214
|
+
puts
|
|
215
|
+
|
|
216
|
+
# Check migration status
|
|
217
|
+
pending = check_migration_status
|
|
218
|
+
puts
|
|
219
|
+
|
|
220
|
+
if pending > 0
|
|
221
|
+
warn "Warning: #{pending} pending migration(s) detected."
|
|
222
|
+
warn " Run 'htm_mcp setup' to apply pending migrations."
|
|
223
|
+
puts
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
puts "Database connection verified!"
|
|
227
|
+
rescue => e
|
|
228
|
+
warn "Verification failed: #{e.message}"
|
|
229
|
+
print_error_suggestion(e.message)
|
|
230
|
+
warn e.backtrace.first(5).join("\n") if ENV['DEBUG']
|
|
231
|
+
exit 1
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def check_migration_status
|
|
236
|
+
migrations_path = File.expand_path('../../../db/migrate', __dir__)
|
|
237
|
+
|
|
238
|
+
# Get available migrations from files
|
|
239
|
+
available_migrations = Dir.glob(File.join(migrations_path, '*.rb')).map do |file|
|
|
240
|
+
{
|
|
241
|
+
version: File.basename(file).split('_').first,
|
|
242
|
+
name: File.basename(file, '.rb')
|
|
243
|
+
}
|
|
244
|
+
end.sort_by { |m| m[:version] }
|
|
245
|
+
|
|
246
|
+
# Ensure ActiveRecord connection for migration check
|
|
247
|
+
HTM::ActiveRecordConfig.establish_connection!
|
|
248
|
+
|
|
249
|
+
# Get applied migrations from database
|
|
250
|
+
applied_versions = begin
|
|
251
|
+
ActiveRecord::Base.connection.select_values('SELECT version FROM schema_migrations ORDER BY version')
|
|
252
|
+
rescue ActiveRecord::StatementInvalid
|
|
253
|
+
[]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
puts "Migration Status"
|
|
257
|
+
puts "-" * 80
|
|
258
|
+
|
|
259
|
+
if available_migrations.empty?
|
|
260
|
+
puts " No migration files found"
|
|
261
|
+
return 0
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
available_migrations.each do |migration|
|
|
265
|
+
applied = applied_versions.include?(migration[:version])
|
|
266
|
+
status_mark = applied ? "+" : "-"
|
|
267
|
+
puts " #{status_mark} #{migration[:name]}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
applied_count = applied_versions.length
|
|
271
|
+
pending_count = available_migrations.length - applied_count
|
|
272
|
+
|
|
273
|
+
puts "-" * 80
|
|
274
|
+
puts " #{applied_count} applied, #{pending_count} pending"
|
|
275
|
+
|
|
276
|
+
pending_count
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def output_default_config
|
|
280
|
+
defaults_path = File.expand_path('../config/defaults.yml', __dir__)
|
|
281
|
+
if File.exist?(defaults_path)
|
|
282
|
+
puts File.read(defaults_path)
|
|
283
|
+
else
|
|
284
|
+
warn "Error: defaults.yml not found at #{defaults_path}"
|
|
285
|
+
exit 1
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def load_config_file(path)
|
|
290
|
+
unless File.exist?(path)
|
|
291
|
+
warn "Error: Config file not found: #{path}"
|
|
292
|
+
exit 1
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
begin
|
|
296
|
+
require 'yaml'
|
|
297
|
+
config_data = YAML.safe_load(
|
|
298
|
+
File.read(path),
|
|
299
|
+
permitted_classes: [Symbol],
|
|
300
|
+
symbolize_names: true,
|
|
301
|
+
aliases: true
|
|
302
|
+
) || {}
|
|
303
|
+
|
|
304
|
+
# Determine which section to use based on environment
|
|
305
|
+
env = HTM::Config.env.to_sym
|
|
306
|
+
base = config_data[:defaults] || {}
|
|
307
|
+
env_overrides = config_data[env] || {}
|
|
308
|
+
|
|
309
|
+
# Merge base with environment-specific overrides
|
|
310
|
+
merged = deep_merge(base, env_overrides)
|
|
311
|
+
|
|
312
|
+
apply_config(merged)
|
|
313
|
+
|
|
314
|
+
warn "Loaded configuration from: #{path}"
|
|
315
|
+
warn "Environment: #{env}"
|
|
316
|
+
rescue => e
|
|
317
|
+
warn "Error loading config file: #{e.message}"
|
|
318
|
+
warn e.backtrace.first(5).join("\n") if ENV['DEBUG']
|
|
319
|
+
exit 1
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def deep_merge(base, override)
|
|
324
|
+
base.merge(override) do |_key, old_val, new_val|
|
|
325
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
326
|
+
deep_merge(old_val, new_val)
|
|
327
|
+
else
|
|
328
|
+
new_val.nil? ? old_val : new_val
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def apply_config(config)
|
|
334
|
+
HTM.configure do |c|
|
|
335
|
+
# Apply nested sections
|
|
336
|
+
apply_section(c, :database, config[:database])
|
|
337
|
+
apply_section(c, :service, config[:service])
|
|
338
|
+
apply_section(c, :embedding, config[:embedding])
|
|
339
|
+
apply_section(c, :tag, config[:tag])
|
|
340
|
+
apply_section(c, :proposition, config[:proposition])
|
|
341
|
+
apply_section(c, :chunking, config[:chunking])
|
|
342
|
+
apply_section(c, :circuit_breaker, config[:circuit_breaker])
|
|
343
|
+
apply_section(c, :relevance, config[:relevance])
|
|
344
|
+
apply_section(c, :job, config[:job])
|
|
345
|
+
apply_section(c, :providers, config[:providers])
|
|
346
|
+
|
|
347
|
+
# Apply top-level scalars
|
|
348
|
+
c.week_start = config[:week_start] if config[:week_start]
|
|
349
|
+
c.connection_timeout = config[:connection_timeout] if config[:connection_timeout]
|
|
350
|
+
c.telemetry_enabled = config[:telemetry_enabled] unless config[:telemetry_enabled].nil?
|
|
351
|
+
c.log_level = config[:log_level] if config[:log_level]
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def apply_section(config, section_name, values)
|
|
356
|
+
return unless values.is_a?(Hash)
|
|
357
|
+
|
|
358
|
+
section = config.send(section_name)
|
|
359
|
+
values.each do |key, value|
|
|
360
|
+
next if value.nil?
|
|
361
|
+
|
|
362
|
+
if value.is_a?(Hash)
|
|
363
|
+
# Handle nested sections (like providers.openai)
|
|
364
|
+
subsection = section.send(key)
|
|
365
|
+
value.each do |subkey, subvalue|
|
|
366
|
+
subsection.send("#{subkey}=", subvalue) unless subvalue.nil?
|
|
367
|
+
end
|
|
368
|
+
else
|
|
369
|
+
section.send("#{key}=", value)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def run_stats
|
|
375
|
+
puts "HTM Memory Statistics"
|
|
376
|
+
puts "====================="
|
|
377
|
+
puts
|
|
378
|
+
|
|
379
|
+
check_database_config!
|
|
380
|
+
|
|
381
|
+
begin
|
|
382
|
+
HTM::ActiveRecordConfig.establish_connection!
|
|
383
|
+
|
|
384
|
+
total_nodes = HTM::Models::Node.count
|
|
385
|
+
deleted_nodes = HTM::Models::Node.deleted.count
|
|
386
|
+
with_embeddings = HTM::Models::Node.with_embeddings.count
|
|
387
|
+
total_tags = HTM::Models::Tag.count
|
|
388
|
+
total_robots = HTM::Models::Robot.count
|
|
389
|
+
total_files = HTM::Models::FileSource.count
|
|
390
|
+
|
|
391
|
+
# Get database size
|
|
392
|
+
db_size = ActiveRecord::Base.connection.execute(
|
|
393
|
+
"SELECT pg_size_pretty(pg_database_size(current_database())) AS size"
|
|
394
|
+
).first['size']
|
|
395
|
+
|
|
396
|
+
puts "Nodes: #{total_nodes} active, #{deleted_nodes} deleted, #{with_embeddings} with embeddings"
|
|
397
|
+
puts "Tags: #{total_tags}"
|
|
398
|
+
puts "Robots: #{total_robots}"
|
|
399
|
+
puts "Files: #{total_files}"
|
|
400
|
+
puts
|
|
401
|
+
puts "Database size: #{db_size}"
|
|
402
|
+
rescue => e
|
|
403
|
+
warn "Stats failed: #{e.message}"
|
|
404
|
+
print_error_suggestion(e.message)
|
|
405
|
+
warn e.backtrace.first(5).join("\n") if ENV['DEBUG']
|
|
406
|
+
exit 1
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def run(args)
|
|
411
|
+
args = args.dup
|
|
412
|
+
|
|
413
|
+
# Handle -c / --config option first (can be combined with other commands)
|
|
414
|
+
config_loaded = handle_config_option(args)
|
|
415
|
+
|
|
416
|
+
# Process remaining command
|
|
417
|
+
case args[0]&.downcase
|
|
418
|
+
when 'help', '-h', '--help'
|
|
419
|
+
print_help
|
|
420
|
+
when 'version', '-v', '--version'
|
|
421
|
+
puts "HTM #{HTM::VERSION}"
|
|
422
|
+
when 'setup', 'init'
|
|
423
|
+
run_setup
|
|
424
|
+
when 'verify'
|
|
425
|
+
run_verify
|
|
426
|
+
when 'stats'
|
|
427
|
+
run_stats
|
|
428
|
+
when 'config'
|
|
429
|
+
output_default_config
|
|
430
|
+
when 'server', 'stdio', nil
|
|
431
|
+
# Return false to indicate server should start
|
|
432
|
+
# 'stdio' is accepted for compatibility with MCP clients that pass it as an argument
|
|
433
|
+
return false
|
|
434
|
+
when /^-/
|
|
435
|
+
$stderr.puts "Unknown option: #{args[0]}"
|
|
436
|
+
$stderr.puts "Run 'htm_mcp help' for usage."
|
|
437
|
+
exit 1
|
|
438
|
+
else
|
|
439
|
+
$stderr.puts "Unknown command: #{args[0]}"
|
|
440
|
+
$stderr.puts "Run 'htm_mcp help' for usage."
|
|
441
|
+
exit 1
|
|
442
|
+
end
|
|
443
|
+
true
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Handle -c / --config option, modifying args in place
|
|
447
|
+
# Returns true if config was loaded, nil otherwise
|
|
448
|
+
def handle_config_option(args)
|
|
449
|
+
config_idx = args.index('-c') || args.index('--config')
|
|
450
|
+
return nil unless config_idx
|
|
451
|
+
|
|
452
|
+
# Remove the -c/--config flag
|
|
453
|
+
args.delete_at(config_idx)
|
|
454
|
+
|
|
455
|
+
# Check if next arg is a path (not another flag or command)
|
|
456
|
+
next_arg = args[config_idx]
|
|
457
|
+
|
|
458
|
+
if next_arg.nil? || next_arg.start_with?('-') || command?(next_arg)
|
|
459
|
+
# No path provided - output default config and exit
|
|
460
|
+
output_default_config
|
|
461
|
+
exit 0
|
|
462
|
+
else
|
|
463
|
+
# Path provided - load config file
|
|
464
|
+
config_path = args.delete_at(config_idx)
|
|
465
|
+
load_config_file(config_path)
|
|
466
|
+
true
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def command?(arg)
|
|
471
|
+
%w[help version setup init verify stats config server stdio].include?(arg.downcase)
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|