254 lines
8.6 KiB
Python
254 lines
8.6 KiB
Python
"""
|
|
Configuration settings handler for Clover - A terminal assistant for AI-powered project management
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
def load_config():
|
|
"""
|
|
Load configuration from .env file and environment variables, with fallback defaults.
|
|
|
|
Returns:
|
|
dict: Configuration dictionary with loaded or default values
|
|
"""
|
|
# Load .env file if it exists
|
|
env_file = Path(__file__).parent.parent / ".env"
|
|
if env_file.exists():
|
|
load_dotenv(env_file)
|
|
else:
|
|
# Try to load from current directory as fallback
|
|
load_dotenv()
|
|
|
|
config = {
|
|
"timeout": int(os.getenv("CLOVER_TIMEOUT", "300")),
|
|
"threads": int(os.getenv("CLOVER_THREADS", "5")),
|
|
"model": os.getenv("CLOVER_MODEL", "qwen3-coder:30b"),
|
|
"api_key": os.getenv("CLOVER_API_KEY"),
|
|
"base_url": os.getenv(
|
|
"CLOVER_BASE_URL", "error-set-CLOVER_BASE_URL-in-.env-file"
|
|
),
|
|
"debug": os.getenv("CLOVER_DEBUG", "false").lower() == "true",
|
|
"verbose": os.getenv("CLOVER_VERBOSE", "false").lower() == "true",
|
|
"cache_enabled": os.getenv("CLOVER_CACHE_ENABLED", "true").lower() == "true",
|
|
"cache_size": int(os.getenv("CLOVER_CACHE_SIZE", "1000")),
|
|
"allow_command_execution": os.getenv(
|
|
"CLOVER_ALLOW_COMMAND_EXECUTION", "true"
|
|
).lower()
|
|
== "true",
|
|
"require_confirmation": os.getenv("CLOVER_REQUIRE_CONFIRMATION", "true").lower()
|
|
== "true",
|
|
"ssl_verify": os.getenv("CLOVER_SSL_VERIFY", "true").lower() == "true",
|
|
"project_root": os.getenv("CLOVER_PROJECT_ROOT", "."),
|
|
"summary_file": os.getenv("CLOVER_SUMMARY_FILE", "clover.md"),
|
|
"structure_file": os.getenv("CLOVER_STRUCTURE_FILE", "structure.md"),
|
|
"max_history_messages": int(
|
|
os.getenv("CLOVER_MAX_HISTORY_MESSAGES", "20")
|
|
),
|
|
"max_history_tokens": int(os.getenv("CLOVER_MAX_HISTORY_TOKENS", "8000")),
|
|
"max_retries": int(os.getenv("CLOVER_MAX_RETRIES", "3")),
|
|
"retry_base_delay": float(os.getenv("CLOVER_RETRY_BASE_DELAY", "2")),
|
|
}
|
|
|
|
# Debug output if enabled
|
|
if config["debug"]:
|
|
print(f"Debug: Loaded configuration:")
|
|
for key, value in config.items():
|
|
if key == "api_key" and value:
|
|
print(f" {key}: {'*' * len(str(value))}")
|
|
else:
|
|
print(f" {key}: {value}")
|
|
|
|
return config
|
|
|
|
|
|
def save_config(config):
|
|
"""
|
|
Save configuration to .env file.
|
|
|
|
Args:
|
|
config (dict): Configuration dictionary to save
|
|
"""
|
|
try:
|
|
env_file = Path(__file__).parent.parent / ".env"
|
|
|
|
# Read existing .env file if it exists
|
|
existing_lines = []
|
|
if env_file.exists():
|
|
with open(env_file, "r") as f:
|
|
existing_lines = f.readlines()
|
|
|
|
# Update or add configuration values
|
|
updated_lines = []
|
|
config_keys_set = set()
|
|
|
|
for line in existing_lines:
|
|
line = line.strip()
|
|
if line and not line.startswith("#") and "=" in line:
|
|
key = line.split("=")[0].strip()
|
|
|
|
# Check if this is a config key we want to update
|
|
config_key = None
|
|
if key == "CLOVER_TIMEOUT":
|
|
config_key = "timeout"
|
|
elif key == "CLOVER_THREADS":
|
|
config_key = "threads"
|
|
elif key == "CLOVER_MODEL":
|
|
config_key = "model"
|
|
elif key == "CLOVER_API_KEY":
|
|
config_key = "api_key"
|
|
elif key == "CLOVER_BASE_URL":
|
|
config_key = "base_url"
|
|
elif key == "CLOVER_DEBUG":
|
|
config_key = "debug"
|
|
elif key == "CLOVER_VERBOSE":
|
|
config_key = "verbose"
|
|
elif key == "CLOVER_CACHE_ENABLED":
|
|
config_key = "cache_enabled"
|
|
elif key == "CLOVER_CACHE_SIZE":
|
|
config_key = "cache_size"
|
|
elif key == "CLOVER_ALLOW_COMMAND_EXECUTION":
|
|
config_key = "allow_command_execution"
|
|
elif key == "CLOVER_REQUIRE_CONFIRMATION":
|
|
config_key = "require_confirmation"
|
|
elif key == "CLOVER_PROJECT_ROOT":
|
|
config_key = "project_root"
|
|
elif key == "CLOVER_SUMMARY_FILE":
|
|
config_key = "summary_file"
|
|
elif key == "CLOVER_STRUCTURE_FILE":
|
|
config_key = "structure_file"
|
|
|
|
if config_key and config_key in config:
|
|
# Update with new value
|
|
value = config[config_key]
|
|
if isinstance(value, bool):
|
|
value = str(value).lower()
|
|
elif value is None:
|
|
value = ""
|
|
updated_lines.append(f"{key}={value}\n")
|
|
config_keys_set.add(config_key)
|
|
else:
|
|
# Keep existing line
|
|
updated_lines.append(line + "\n")
|
|
else:
|
|
# Keep comments and empty lines
|
|
updated_lines.append(line + "\n")
|
|
|
|
# Add any new configuration keys that weren't in the file
|
|
new_configs = {
|
|
"timeout": "CLOVER_TIMEOUT",
|
|
"threads": "CLOVER_THREADS",
|
|
"model": "CLOVER_MODEL",
|
|
"api_key": "CLOVER_API_KEY",
|
|
"base_url": "CLOVER_BASE_URL",
|
|
"debug": "CLOVER_DEBUG",
|
|
"verbose": "CLOVER_VERBOSE",
|
|
"cache_enabled": "CLOVER_CACHE_ENABLED",
|
|
"cache_size": "CLOVER_CACHE_SIZE",
|
|
"allow_command_execution": "CLOVER_ALLOW_COMMAND_EXECUTION",
|
|
"require_confirmation": "CLOVER_REQUIRE_CONFIRMATION",
|
|
"project_root": "CLOVER_PROJECT_ROOT",
|
|
"summary_file": "CLOVER_SUMMARY_FILE",
|
|
"structure_file": "CLOVER_STRUCTURE_FILE",
|
|
}
|
|
|
|
for config_key, env_key in new_configs.items():
|
|
if config_key not in config_keys_set and config_key in config:
|
|
value = config[config_key]
|
|
if isinstance(value, bool):
|
|
value = str(value).lower()
|
|
elif value is None:
|
|
value = ""
|
|
updated_lines.append(f"{env_key}={value}\n")
|
|
|
|
# Write updated .env file
|
|
with open(env_file, "w") as f:
|
|
f.writelines(updated_lines)
|
|
|
|
print(f"Configuration saved to {env_file}")
|
|
|
|
except Exception as e:
|
|
print(f"Error saving configuration: {e}")
|
|
|
|
|
|
def get_config_value(key, default=None):
|
|
"""
|
|
Get a specific configuration value.
|
|
|
|
Args:
|
|
key (str): Configuration key
|
|
default: Default value if key not found
|
|
|
|
Returns:
|
|
Configuration value or default
|
|
"""
|
|
config = load_config()
|
|
return config.get(key, default)
|
|
|
|
|
|
def set_config_value(key, value):
|
|
"""
|
|
Set a specific configuration value and save to file.
|
|
|
|
Args:
|
|
key (str): Configuration key
|
|
value: Value to set
|
|
"""
|
|
config = load_config()
|
|
config[key] = value
|
|
save_config(config)
|
|
|
|
|
|
def validate_config():
|
|
"""
|
|
Validate configuration and return any issues found.
|
|
|
|
Returns:
|
|
list: List of validation issues (empty if valid)
|
|
"""
|
|
config = load_config()
|
|
issues = []
|
|
|
|
# Check required settings
|
|
base_url = config.get("base_url", "")
|
|
if not base_url or base_url.startswith("error-set-"):
|
|
issues.append(
|
|
"CLOVER_BASE_URL is not set. Set it in your .env file or environment. "
|
|
"Example: CLOVER_BASE_URL=http://localhost:11434"
|
|
)
|
|
|
|
# Check numeric values
|
|
try:
|
|
timeout = int(config.get("timeout", 300))
|
|
if timeout <= 0:
|
|
issues.append("timeout must be positive")
|
|
except (ValueError, TypeError):
|
|
issues.append("timeout must be a valid integer")
|
|
|
|
try:
|
|
threads = int(config.get("threads", 5))
|
|
if threads <= 0 or threads > 50:
|
|
issues.append("threads must be between 1 and 50")
|
|
except (ValueError, TypeError):
|
|
issues.append("threads must be a valid integer")
|
|
|
|
# Check cache size
|
|
try:
|
|
cache_size = int(config.get("cache_size", 1000))
|
|
if cache_size < 0:
|
|
issues.append("cache_size must be non-negative")
|
|
except (ValueError, TypeError):
|
|
issues.append("cache_size must be a valid integer")
|
|
|
|
return issues
|
|
|
|
|
|
# For backwards compatibility
|
|
def load_settings():
|
|
"""Legacy function name - use load_config() instead"""
|
|
return load_config()
|