Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 33 additions & 58 deletions backends/advanced/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(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)

def print_header(self, title: str):
"""Print a colorful header"""
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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"""
Expand All @@ -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":
Expand All @@ -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")

Expand All @@ -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")

Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
20 changes: 11 additions & 9 deletions wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]")
Expand Down