From a20c1d73c7ac976f9626268d2ec99b5fdc6e1a62 Mon Sep 17 00:00:00 2001 From: Ankush Malaker <43288948+AnkushMalaker@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:09:19 +0000 Subject: [PATCH 1/2] Refactor configuration management in wizard and ChronicleSetup - Updated wizard.py to read Obsidian/Neo4j configuration from config.yml, enhancing flexibility and error handling. - Refactored ChronicleSetup to utilize ConfigManager for loading and verifying config.yml, ensuring a single source of truth. - Improved user feedback for missing configuration files and streamlined the setup process for memory and transcription providers. --- backends/advanced/init.py | 91 ++++++++++++++------------------------- wizard.py | 20 +++++---- 2 files changed, 44 insertions(+), 67 deletions(-) diff --git a/backends/advanced/init.py b/backends/advanced/init.py index f093bf4d..f8231db8 100644 --- a/backends/advanced/init.py +++ b/backends/advanced/init.py @@ -33,22 +33,21 @@ def __init__(self, args=None): self.config: Dict[str, Any] = {} self.args = args or argparse.Namespace() self.config_yml_path = Path("../../config/config.yml") # Main config at config/config.yml - self.config_yml_data = None # Check if we're in the right directory if not Path("pyproject.toml").exists() or not Path("src").exists(): self.console.print("[red][ERROR][/red] Please run this script from the backends/advanced directory") sys.exit(1) - # Initialize ConfigManager + # Initialize ConfigManager (single source of truth for config.yml) self.config_manager = ConfigManager(service_path="backends/advanced") self.console.print(f"[blue][INFO][/blue] Using config.yml at: {self.config_manager.config_yml_path}") - # Load existing config or create default structure - self.config_yml_data = self.config_manager.get_full_config() - if not self.config_yml_data: - self.console.print("[yellow][WARNING][/yellow] config.yml not found, will create default structure") - self.config_yml_data = self._get_default_config_structure() + # Verify config.yml exists - fail fast if missing + if not self.config_manager.config_yml_path.exists(): + self.console.print("[red][ERROR][/red] config.yml not found at {self.config_manager.config_yml_path}") + self.console.print("[red][ERROR][/red] Run wizard.py from project root to create config.yml") + sys.exit(1) def print_header(self, title: str): """Print a colorful header""" @@ -138,28 +137,6 @@ def mask_api_key(self, key: str, show_chars: int = 5) -> str: return f"{key_clean[:show_chars]}{'*' * min(15, len(key_clean) - show_chars * 2)}{key_clean[-show_chars:]}" - def _get_default_config_structure(self) -> Dict[str, Any]: - """Return default config.yml structure if file doesn't exist""" - return { - "defaults": { - "llm": "openai-llm", - "embedding": "openai-embed", - "stt": "stt-deepgram", - "tts": "tts-http", - "vector_store": "vs-qdrant" - }, - "models": [], - "memory": { - "provider": "chronicle", - "timeout_seconds": 1200, - "extraction": { - "enabled": True, - "prompt": "Extract important information from this conversation and return a JSON object with an array named \"facts\"." - } - } - } - - def setup_authentication(self): """Configure authentication settings""" self.print_section("Authentication Setup") @@ -208,7 +185,6 @@ def setup_transcription(self): # Update config.yml to use Deepgram self.config_manager.update_config_defaults({"stt": "stt-deepgram"}) - self.config_yml_data = self.config_manager.get_full_config() # Reload self.console.print("[green][SUCCESS][/green] Deepgram configured in config.yml and .env") self.console.print("[blue][INFO][/blue] Set defaults.stt: stt-deepgram") @@ -224,7 +200,6 @@ def setup_transcription(self): # Update config.yml to use Parakeet self.config_manager.update_config_defaults({"stt": "stt-parakeet-batch"}) - self.config_yml_data = self.config_manager.get_full_config() # Reload self.console.print("[green][SUCCESS][/green] Parakeet configured in config.yml and .env") self.console.print("[blue][INFO][/blue] Set defaults.stt: stt-parakeet-batch") @@ -266,7 +241,6 @@ def setup_llm(self): self.config["OPENAI_API_KEY"] = api_key # Update config.yml to use OpenAI models self.config_manager.update_config_defaults({"llm": "openai-llm", "embedding": "openai-embed"}) - self.config_yml_data = self.config_manager.get_full_config() # Reload to stay in sync self.console.print("[green][SUCCESS][/green] OpenAI configured in config.yml") self.console.print("[blue][INFO][/blue] Set defaults.llm: openai-llm") self.console.print("[blue][INFO][/blue] Set defaults.embedding: openai-embed") @@ -277,7 +251,6 @@ def setup_llm(self): self.console.print("[blue][INFO][/blue] Ollama selected") # Update config.yml to use Ollama models self.config_manager.update_config_defaults({"llm": "local-llm", "embedding": "local-embed"}) - self.config_yml_data = self.config_manager.get_full_config() # Reload to stay in sync self.console.print("[green][SUCCESS][/green] Ollama configured in config.yml") self.console.print("[blue][INFO][/blue] Set defaults.llm: local-llm") self.console.print("[blue][INFO][/blue] Set defaults.embedding: local-embed") @@ -287,7 +260,6 @@ def setup_llm(self): self.console.print("[blue][INFO][/blue] Skipping LLM setup - memory extraction disabled") # Disable memory extraction in config.yml self.config_manager.update_memory_config({"extraction": {"enabled": False}}) - self.config_yml_data = self.config_manager.get_full_config() # Reload to stay in sync def setup_memory(self): """Configure memory provider - updates config.yml""" @@ -309,7 +281,6 @@ def setup_memory(self): # Update config.yml (also updates .env automatically) self.config_manager.update_memory_config({"provider": "chronicle"}) - self.config_yml_data = self.config_manager.get_full_config() # Reload to stay in sync self.console.print("[green][SUCCESS][/green] Chronicle memory provider configured in config.yml and .env") elif choice == "2": @@ -330,7 +301,6 @@ def setup_memory(self): "timeout": int(timeout) } }) - self.config_yml_data = self.config_manager.get_full_config() # Reload to stay in sync self.console.print("[green][SUCCESS][/green] OpenMemory MCP configured in config.yml and .env") self.console.print("[yellow][WARNING][/yellow] Remember to start OpenMemory: cd ../../extras/openmemory-mcp && docker compose up -d") @@ -348,7 +318,6 @@ def setup_memory(self): "timeout": int(timeout) } }) - self.config_yml_data = self.config_manager.get_full_config() # Reload to stay in sync self.console.print("[green][SUCCESS][/green] Mycelia memory provider configured in config.yml and .env") self.console.print("[yellow][WARNING][/yellow] Make sure Mycelia is running at the configured URL") @@ -405,21 +374,19 @@ def setup_obsidian(self): neo4j_password = self.prompt_password("Neo4j password (min 8 chars)") if enable_obsidian: - # Update .env with credentials - self.config["OBSIDIAN_ENABLED"] = "true" + # Update .env with credentials only (secrets, not feature flags) self.config["NEO4J_HOST"] = "neo4j-mem0" self.config["NEO4J_USER"] = "neo4j" self.config["NEO4J_PASSWORD"] = neo4j_password - # Update config.yml with feature flag - if "memory" not in self.config_yml_data: - self.config_yml_data["memory"] = {} - if "obsidian" not in self.config_yml_data["memory"]: - self.config_yml_data["memory"]["obsidian"] = {} - - self.config_yml_data["memory"]["obsidian"]["enabled"] = True - self.config_yml_data["memory"]["obsidian"]["neo4j_host"] = "neo4j-mem0" - self.config_yml_data["memory"]["obsidian"]["timeout"] = 30 + # Update config.yml with feature flag (source of truth) - auto-saves via ConfigManager + self.config_manager.update_memory_config({ + "obsidian": { + "enabled": True, + "neo4j_host": "neo4j-mem0", + "timeout": 30 + } + }) self.console.print("[green][SUCCESS][/green] Obsidian/Neo4j configured") self.console.print("[blue][INFO][/blue] Neo4j will start automatically with --profile obsidian") @@ -585,28 +552,32 @@ def show_summary(self): self.console.print(f"✅ Admin Account: {self.config.get('ADMIN_EMAIL', 'Not configured')}") + # Get current config from ConfigManager (single source of truth) + config_yml = self.config_manager.get_full_config() + # Show transcription from config.yml - stt_default = self.config_yml_data.get("defaults", {}).get("stt", "not set") + stt_default = config_yml.get("defaults", {}).get("stt", "not set") stt_model = next( - (m for m in self.config_yml_data.get("models", []) if m.get("name") == stt_default), + (m for m in config_yml.get("models", []) if m.get("name") == stt_default), None ) stt_provider = stt_model.get("model_provider", "unknown") if stt_model else "not configured" self.console.print(f"✅ Transcription: {stt_provider} ({stt_default}) - config.yml") # Show LLM config from config.yml - llm_default = self.config_yml_data.get("defaults", {}).get("llm", "not set") - embedding_default = self.config_yml_data.get("defaults", {}).get("embedding", "not set") + llm_default = config_yml.get("defaults", {}).get("llm", "not set") + embedding_default = config_yml.get("defaults", {}).get("embedding", "not set") self.console.print(f"✅ LLM: {llm_default} (config.yml)") self.console.print(f"✅ Embedding: {embedding_default} (config.yml)") # Show memory provider from config.yml - memory_provider = self.config_yml_data.get("memory", {}).get("provider", "chronicle") + memory_provider = config_yml.get("memory", {}).get("provider", "chronicle") self.console.print(f"✅ Memory Provider: {memory_provider} (config.yml)") - # Show Obsidian/Neo4j status - if self.config.get('OBSIDIAN_ENABLED') == 'true': - neo4j_host = self.config.get('NEO4J_HOST', 'not set') + # Show Obsidian/Neo4j status (read from config.yml) + obsidian_config = config_yml.get("memory", {}).get("obsidian", {}) + if obsidian_config.get("enabled", False): + neo4j_host = obsidian_config.get("neo4j_host", "not set") self.console.print(f"✅ Obsidian/Neo4j: Enabled ({neo4j_host})") # Auto-determine URLs based on HTTPS configuration @@ -625,9 +596,13 @@ def show_next_steps(self): self.print_section("Next Steps") self.console.print() + # Get current config from ConfigManager (single source of truth) + config_yml = self.config_manager.get_full_config() + self.console.print("1. Start the main services:") - # Include --profile obsidian if Obsidian is enabled - if self.config.get('OBSIDIAN_ENABLED') == 'true': + # Include --profile obsidian if Obsidian is enabled (read from config.yml) + obsidian_enabled = config_yml.get("memory", {}).get("obsidian", {}).get("enabled", False) + if obsidian_enabled: self.console.print(" [cyan]docker compose --profile obsidian up --build -d[/cyan]") self.console.print(" [dim](Includes Neo4j for Obsidian integration)[/dim]") else: diff --git a/wizard.py b/wizard.py index d78a910c..a2e2b2f7 100755 --- a/wizard.py +++ b/wizard.py @@ -9,6 +9,7 @@ import sys from datetime import datetime from pathlib import Path +import yaml from dotenv import get_key from rich import print as rprint @@ -449,17 +450,18 @@ def main(): else: failed_services.append(service) - # Check for Obsidian/Neo4j configuration + # Check for Obsidian/Neo4j configuration (read from config.yml) obsidian_enabled = False if 'advanced' in selected_services and 'advanced' not in failed_services: - backend_env_path = Path('backends/advanced/.env') - if backend_env_path.exists(): - neo4j_host = read_env_value(str(backend_env_path), 'NEO4J_HOST') - obsidian_enabled_flag = read_env_value(str(backend_env_path), 'OBSIDIAN_ENABLED') - if neo4j_host and not is_placeholder(neo4j_host, 'your-neo4j-host-here', 'your_neo4j_host_here'): - obsidian_enabled = True - elif obsidian_enabled_flag == 'true': - obsidian_enabled = True + config_yml_path = Path('config/config.yml') + if config_yml_path.exists(): + try: + with open(config_yml_path, 'r') as f: + config_data = yaml.safe_load(f) + obsidian_config = config_data.get('memory', {}).get('obsidian', {}) + obsidian_enabled = obsidian_config.get('enabled', False) + except Exception as e: + console.print(f"[yellow]Warning: Could not read config.yml: {e}[/yellow]") # Final Summary console.print(f"\n🎊 [bold green]Setup Complete![/bold green]") From ad4b1f95ff71c923bf5c37de0def7da188c6b75a Mon Sep 17 00:00:00 2001 From: Ankush Malaker <43288948+AnkushMalaker@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:26:38 +0000 Subject: [PATCH 2/2] Fix string formatting for error message in ChronicleSetup --- backends/advanced/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/advanced/init.py b/backends/advanced/init.py index f8231db8..fe04fd15 100644 --- a/backends/advanced/init.py +++ b/backends/advanced/init.py @@ -45,7 +45,7 @@ def __init__(self, args=None): # Verify config.yml exists - fail fast if missing if not self.config_manager.config_yml_path.exists(): - self.console.print("[red][ERROR][/red] config.yml not found at {self.config_manager.config_yml_path}") + self.console.print(f"[red][ERROR][/red] config.yml not found at {self.config_manager.config_yml_path}") self.console.print("[red][ERROR][/red] Run wizard.py from project root to create config.yml") sys.exit(1)