codex_memory/application/
command_handlers.rs

1use crate::application::DependencyContainer;
2use anyhow::Result;
3use std::sync::Arc;
4use tracing::{error, info, warn};
5
6/// Clean separation of command handling logic from main.rs
7pub struct SetupCommandHandler {
8    container: Arc<DependencyContainer>,
9}
10
11impl SetupCommandHandler {
12    pub fn new(container: Arc<DependencyContainer>) -> Self {
13        Self { container }
14    }
15
16    pub async fn run_setup(
17        &self,
18        force: bool,
19        skip_database: bool,
20        skip_models: bool,
21    ) -> Result<()> {
22        info!("๐Ÿš€ Starting Agentic Memory System setup...");
23
24        if !force {
25            match self.container.health_check().await {
26                Ok(true) => {
27                    info!(
28                        "โœ… System appears to be already set up. Use --force to run setup anyway."
29                    );
30                    return Ok(());
31                }
32                _ => {
33                    info!("๐Ÿ”ง System needs setup, proceeding...");
34                }
35            }
36        }
37
38        if !skip_models {
39            self.container.setup_manager.run_setup().await?;
40        }
41
42        if !skip_database {
43            self.container.database_setup.setup().await?;
44        }
45
46        info!("๐ŸŽ‰ Setup completed successfully!");
47        info!("๐Ÿ’ก You can now start the server with: codex-memory start");
48
49        Ok(())
50    }
51}
52
53pub struct HealthCommandHandler {
54    container: Arc<DependencyContainer>,
55}
56
57impl HealthCommandHandler {
58    pub fn new(container: Arc<DependencyContainer>) -> Self {
59        Self { container }
60    }
61
62    pub async fn run_health_check(&self, detailed: bool) -> Result<()> {
63        info!("๐Ÿฅ Running system health check...");
64
65        if detailed {
66            let health = self.container.health_checker.check_system_health().await?;
67
68            info!("๐Ÿ“Š System Health: {:?}", health.status);
69            info!("โฑ๏ธ  Uptime: {} seconds", health.uptime_seconds);
70            info!(
71                "๐Ÿ’พ Memory Usage: {} MB",
72                health.memory_usage_bytes / (1024 * 1024)
73            );
74            info!("๐Ÿ”ฅ CPU Usage: {:.1}%", health.cpu_usage_percent);
75
76            for (component, component_health) in &health.components {
77                match component_health.status {
78                    crate::monitoring::HealthStatus::Healthy => {
79                        info!("โœ… {}: Healthy", component);
80                    }
81                    crate::monitoring::HealthStatus::Degraded => {
82                        warn!(
83                            "โš ๏ธ  {}: Degraded - {:?}",
84                            component, component_health.message
85                        );
86                    }
87                    crate::monitoring::HealthStatus::Unhealthy => {
88                        error!(
89                            "โŒ {}: Unhealthy - {:?}",
90                            component, component_health.message
91                        );
92                    }
93                }
94            }
95        } else {
96            match self.container.health_check().await {
97                Ok(true) => info!("โœ… System is healthy"),
98                _ => error!("โŒ System health check failed"),
99            }
100        }
101
102        Ok(())
103    }
104}
105
106pub struct ModelCommandHandler {
107    container: Arc<DependencyContainer>,
108}
109
110impl ModelCommandHandler {
111    pub fn new(container: Arc<DependencyContainer>) -> Self {
112        Self { container }
113    }
114
115    pub async fn list_models(&self) -> Result<()> {
116        self.container.setup_manager.list_available_models().await
117    }
118}
119
120pub struct DatabaseCommandHandler {
121    container: Arc<DependencyContainer>,
122}
123
124impl DatabaseCommandHandler {
125    pub fn new(container: Arc<DependencyContainer>) -> Self {
126        Self { container }
127    }
128
129    pub async fn setup(&self) -> Result<()> {
130        self.container.database_setup.setup().await
131    }
132
133    pub async fn health(&self) -> Result<()> {
134        let health = self.container.database_setup.health_check().await?;
135        info!("๐Ÿ“Š Database Health: {}", health.status_summary());
136        info!(
137            "   - Connectivity: {}",
138            if health.connectivity { "โœ…" } else { "โŒ" }
139        );
140        info!(
141            "   - pgvector: {}",
142            if health.pgvector_installed {
143                "โœ…"
144            } else {
145                "โŒ"
146            }
147        );
148        info!(
149            "   - Schema: {}",
150            if health.schema_ready { "โœ…" } else { "โŒ" }
151        );
152        info!("   - Memory count: {}", health.memory_count);
153        Ok(())
154    }
155
156    pub async fn migrate(&self) -> Result<()> {
157        error!("โŒ Migration support not available in this build");
158        info!("๐Ÿ’ก Use direct SQL or database tools to run migrations");
159        Err(anyhow::anyhow!("Migration support not compiled in"))
160    }
161}
162
163pub struct McpCommandHandler {
164    container: Arc<DependencyContainer>,
165}
166
167impl McpCommandHandler {
168    pub fn new(container: Arc<DependencyContainer>) -> Self {
169        Self { container }
170    }
171
172    pub async fn validate(&self) -> Result<()> {
173        info!("๐Ÿ” Validating MCP configuration...");
174        match self.container.config.validate_mcp_environment() {
175            Ok(_) => {
176                info!("โœ… MCP configuration is valid");
177                info!(
178                    "   - Database: {}",
179                    self.container.config.safe_database_url()
180                );
181                info!(
182                    "   - Embedding: {} ({})",
183                    self.container.config.embedding.provider, self.container.config.embedding.model
184                );
185                info!("   - HTTP Port: {}", self.container.config.http_port);
186                if let Some(mcp_port) = self.container.config.mcp_port {
187                    info!("   - MCP Port: {}", mcp_port);
188                }
189                Ok(())
190            }
191            Err(e) => {
192                error!("โŒ MCP configuration validation failed: {}", e);
193                std::process::exit(1);
194            }
195        }
196    }
197
198    pub async fn diagnose(&self) -> Result<()> {
199        info!("๐Ÿ” Generating MCP diagnostic report...");
200        let report = self.container.config.create_diagnostic_report();
201        println!("{report}");
202        Ok(())
203    }
204
205    pub async fn test(&self) -> Result<()> {
206        info!("๐Ÿงช Testing MCP server connectivity...");
207        self.container.config.validate_mcp_environment()?;
208
209        // Test database
210        info!("Testing database connectivity...");
211        match self.container.database_setup.health_check().await {
212            Ok(health) => {
213                info!(
214                    "โœ… Database: {}",
215                    if health.connectivity {
216                        "Connected"
217                    } else {
218                        "Failed"
219                    }
220                );
221            }
222            Err(e) => {
223                error!("โŒ Database: Connection failed - {}", e);
224            }
225        }
226
227        // Test embedding service
228        info!("Testing embedding service...");
229        match self.container.setup_manager.quick_health_check().await {
230            Ok(_) => info!("โœ… Embedding service: Available"),
231            Err(e) => error!("โŒ Embedding service: {}", e),
232        }
233
234        info!("๐ŸŽ‰ MCP connectivity test completed");
235        Ok(())
236    }
237
238    pub async fn template(&self, template_type: String, output: Option<String>) -> Result<()> {
239        info!(
240            "๐Ÿ“‹ Generating MCP configuration template: {}",
241            template_type
242        );
243
244        let template_content = match template_type.as_str() {
245            "basic" => self.generate_basic_template(),
246            "production" => self.generate_production_template(),
247            "development" => self.generate_development_template(),
248            _ => {
249                error!(
250                    "โŒ Unknown template type: {}. Available: basic, production, development",
251                    template_type
252                );
253                return Err(anyhow::anyhow!("Invalid template type"));
254            }
255        };
256
257        match output {
258            Some(path) => {
259                std::fs::write(&path, template_content)?;
260                info!("โœ… Template written to: {}", path);
261            }
262            None => {
263                println!("{template_content}");
264            }
265        }
266        Ok(())
267    }
268
269    fn generate_basic_template(&self) -> String {
270        r#"{
271  "mcpServers": {
272    "agentic-memory": {
273      "command": "/path/to/codex-memory",
274      "args": ["mcp-stdio"],
275      "env": {
276        "DATABASE_URL": "postgresql://username:password@localhost:5432/memory_db",
277        "EMBEDDING_PROVIDER": "ollama",
278        "EMBEDDING_BASE_URL": "http://localhost:11434",
279        "EMBEDDING_MODEL": "nomic-embed-text",
280        "LOG_LEVEL": "info"
281      }
282    }
283  }
284}"#
285        .to_string()
286    }
287
288    fn generate_production_template(&self) -> String {
289        r#"{
290  "mcpServers": {
291    "agentic-memory": {
292      "command": "/usr/local/bin/codex-memory",
293      "args": ["mcp-stdio"],
294      "env": {
295        "DATABASE_URL": "${DATABASE_URL}",
296        "EMBEDDING_PROVIDER": "${EMBEDDING_PROVIDER:-ollama}",
297        "EMBEDDING_BASE_URL": "${EMBEDDING_BASE_URL:-http://localhost:11434}",
298        "EMBEDDING_MODEL": "${EMBEDDING_MODEL:-nomic-embed-text}",
299        "OPENAI_API_KEY": "${OPENAI_API_KEY}",
300        "LOG_LEVEL": "warn",
301        "MAX_DB_CONNECTIONS": "20",
302        "ENABLE_METRICS": "true"
303      }
304    }
305  }
306}"#
307        .to_string()
308    }
309
310    fn generate_development_template(&self) -> String {
311        r#"{
312  "mcpServers": {
313    "agentic-memory-dev": {
314      "command": "./target/debug/codex-memory",
315      "args": ["mcp-stdio"],
316      "env": {
317        "DATABASE_URL": "postgresql://dev_user:dev_password@localhost:5432/memory_dev_db",
318        "EMBEDDING_PROVIDER": "mock",
319        "EMBEDDING_MODEL": "mock-model",
320        "LOG_LEVEL": "debug",
321        "ENABLE_METRICS": "false"
322      }
323    }
324  }
325}"#
326        .to_string()
327    }
328}
329
330pub struct ManagerCommandHandler {
331    container: Arc<DependencyContainer>,
332}
333
334impl ManagerCommandHandler {
335    pub fn new(container: Arc<DependencyContainer>) -> Self {
336        Self { container }
337    }
338
339    pub async fn start(
340        &self,
341        daemon: bool,
342        pid_file: Option<String>,
343        log_file: Option<String>,
344    ) -> Result<()> {
345        let manager = if pid_file.is_some() || log_file.is_some() {
346            crate::manager::ServerManager::with_paths(pid_file, log_file)
347        } else {
348            crate::manager::ServerManager::new()
349        };
350        manager.start_daemon(daemon).await
351    }
352
353    pub async fn stop(&self, pid_file: Option<String>) -> Result<()> {
354        let manager = if let Some(pid_file) = pid_file {
355            crate::manager::ServerManager::with_paths(Some(pid_file), None)
356        } else {
357            crate::manager::ServerManager::new()
358        };
359        manager.stop().await
360    }
361
362    pub async fn restart(&self, pid_file: Option<String>) -> Result<()> {
363        let manager = if let Some(pid_file) = pid_file {
364            crate::manager::ServerManager::with_paths(Some(pid_file), None)
365        } else {
366            crate::manager::ServerManager::new()
367        };
368        manager.restart().await
369    }
370
371    pub async fn status(&self, detailed: bool) -> Result<()> {
372        self.container.server_manager.status(detailed).await
373    }
374
375    pub async fn logs(&self, lines: usize, follow: bool) -> Result<()> {
376        self.container.server_manager.show_logs(lines, follow).await
377    }
378
379    pub async fn install(&self, service_type: Option<String>) -> Result<()> {
380        self.container
381            .server_manager
382            .install_service(service_type)
383            .await
384    }
385
386    pub async fn uninstall(&self) -> Result<()> {
387        self.container.server_manager.uninstall_service().await
388    }
389}
390
391pub struct ServerCommandHandler {
392    container: Arc<DependencyContainer>,
393}
394
395impl ServerCommandHandler {
396    pub fn new(container: Arc<DependencyContainer>) -> Self {
397        Self { container }
398    }
399
400    pub async fn start_http(&self, skip_setup: bool) -> Result<()> {
401        info!("๐Ÿš€ Starting HTTP server...");
402
403        if !skip_setup {
404            self.validate_system().await?;
405        }
406
407        // The actual HTTP server startup logic would be here
408        // This is kept separate from main.rs business logic
409        info!("HTTP server would start here with container dependencies");
410
411        Ok(())
412    }
413
414    pub async fn start_mcp_stdio(&self, skip_setup: bool) -> Result<()> {
415        info!("๐Ÿš€ Starting MCP stdio server...");
416
417        if !skip_setup {
418            self.container.config.validate()?;
419        }
420
421        let mut mcp_server = self.container.create_mcp_server().await?;
422        mcp_server.start().await?;
423
424        Ok(())
425    }
426
427    async fn validate_system(&self) -> Result<()> {
428        info!("๐Ÿ” Running pre-flight checks...");
429
430        match self.container.setup_manager.quick_health_check().await {
431            Ok(_) => info!("โœ… System health check passed"),
432            Err(e) => {
433                error!("โŒ System health check failed: {}", e);
434                info!("๐Ÿ’ก Try running: codex-memory setup");
435                return Err(e);
436            }
437        }
438
439        match self.container.database_setup.health_check().await {
440            Ok(health) => {
441                if health.is_healthy() {
442                    info!("โœ… Database health check passed");
443                } else {
444                    error!(
445                        "โŒ Database health check failed: {}",
446                        health.status_summary()
447                    );
448                    info!("๐Ÿ’ก Try running: codex-memory database setup");
449                    return Err(anyhow::anyhow!("Database not ready"));
450                }
451            }
452            Err(e) => {
453                error!("โŒ Database connectivity failed: {}", e);
454                return Err(e);
455            }
456        }
457
458        Ok(())
459    }
460}
461
462pub struct BackupCommandHandler {
463    container: Arc<DependencyContainer>,
464}
465
466impl BackupCommandHandler {
467    pub fn new(container: Arc<DependencyContainer>) -> Self {
468        Self { container }
469    }
470
471    pub async fn create_backup(&self) -> Result<()> {
472        if let Some(ref backup_manager) = self.container.backup_manager {
473            let metadata = backup_manager.create_full_backup().await?;
474            info!("โœ… Backup created: {}", metadata.id);
475            Ok(())
476        } else {
477            Err(anyhow::anyhow!("Backup functionality is not enabled"))
478        }
479    }
480
481    pub async fn list_backups(&self) -> Result<()> {
482        if let Some(ref backup_manager) = self.container.backup_manager {
483            let stats = backup_manager.get_backup_statistics().await?;
484            info!("๐Ÿ“Š Total backups: {}", stats.total_backups);
485            info!(
486                "โœ… Successful (last 7 days): {}",
487                stats.successful_backups_last_7_days
488            );
489            info!(
490                "โŒ Failed (last 7 days): {}",
491                stats.failed_backups_last_7_days
492            );
493            Ok(())
494        } else {
495            Err(anyhow::anyhow!("Backup functionality is not enabled"))
496        }
497    }
498}