Files
autarch/core/config.py

741 lines
29 KiB
Python
Raw Permalink Normal View History

2026-03-13 15:17:15 -07:00
"""
AUTARCH Configuration Handler
Manages the autarch_settings.conf file for llama.cpp settings
"""
import os
import configparser
from pathlib import Path
class Config:
"""Configuration manager for AUTARCH settings."""
DEFAULT_CONFIG = {
'llama': {
'model_path': '',
'n_ctx': '4096',
'n_threads': '4',
'n_gpu_layers': '0',
'gpu_backend': 'cpu',
'temperature': '0.7',
'top_p': '0.9',
'top_k': '40',
'repeat_penalty': '1.1',
'max_tokens': '2048',
'seed': '-1',
},
'autarch': {
'first_run': 'true',
'modules_path': 'modules',
'verbose': 'false',
'quiet': 'false',
'no_banner': 'false',
'llm_backend': 'local',
},
'claude': {
'api_key': '',
'model': 'claude-sonnet-4-20250514',
'max_tokens': '4096',
'temperature': '0.7',
},
'osint': {
'max_threads': '8',
'timeout': '8',
'include_nsfw': 'false',
},
'pentest': {
'max_pipeline_steps': '50',
'output_chunk_size': '2000',
'auto_execute': 'false',
'save_raw_output': 'true',
},
'transformers': {
'model_path': '',
'device': 'auto',
'torch_dtype': 'auto',
'load_in_8bit': 'false',
'load_in_4bit': 'false',
'trust_remote_code': 'false',
'max_tokens': '2048',
'temperature': '0.7',
'top_p': '0.9',
'top_k': '40',
'repetition_penalty': '1.1',
},
'rsf': {
'install_path': '',
'enabled': 'true',
'default_target': '',
'default_port': '80',
'execution_timeout': '120',
},
'upnp': {
'enabled': 'true',
'internal_ip': '10.0.0.26',
'refresh_hours': '12',
'mappings': '443:TCP,51820:UDP,8181:TCP',
},
'web': {
'host': '0.0.0.0',
'port': '8181',
'secret_key': '',
'mcp_port': '8081',
},
'mcp': {
'enabled': 'false',
'auto_start': 'false',
'transport': 'sse',
'host': '0.0.0.0',
'port': '8081',
'log_level': 'INFO',
'instructions': 'AUTARCH security framework tools',
'auth_enabled': 'false',
'auth_token': '',
'rate_limit': '',
'mask_errors': 'false',
'request_timeout': '120',
'max_message_size': '10485760',
'cors_origins': '*',
'ssl_enabled': 'false',
'ssl_cert': '',
'ssl_key': '',
'disabled_tools': '',
'nmap_timeout': '120',
'tcpdump_timeout': '30',
'whois_timeout': '15',
'dns_timeout': '10',
'geoip_timeout': '10',
'geoip_endpoint': 'http://ip-api.com/json/',
},
2026-03-13 15:17:15 -07:00
'revshell': {
'enabled': 'true',
'host': '0.0.0.0',
'port': '17322',
'auto_start': 'false',
},
'slm': {
'enabled': 'true',
'backend': 'local',
'model_path': '',
'n_ctx': '512',
'n_gpu_layers': '-1',
'n_threads': '2',
},
'sam': {
'enabled': 'true',
'backend': 'local',
'model_path': '',
'n_ctx': '2048',
'n_gpu_layers': '-1',
'n_threads': '4',
},
'lam': {
'enabled': 'true',
'backend': 'local',
'model_path': '',
'n_ctx': '4096',
'n_gpu_layers': '-1',
'n_threads': '4',
},
'agents': {
'backend': 'local',
'local_max_steps': '20',
'local_verbose': 'true',
'claude_enabled': 'false',
'claude_model': 'claude-sonnet-4-6',
'claude_max_tokens': '16384',
'claude_max_steps': '30',
'openai_enabled': 'false',
'openai_model': 'gpt-4o',
'openai_base_url': 'https://api.openai.com/v1',
'openai_max_tokens': '16384',
'openai_max_steps': '30',
},
'hal_memory': {
'max_bytes': str(4 * 1024 * 1024 * 1024), # 4GB default
},
2026-03-13 15:17:15 -07:00
'autonomy': {
'enabled': 'false',
'monitor_interval': '3',
'rule_eval_interval': '5',
'max_concurrent_agents': '3',
'threat_threshold_auto_respond': '40',
'log_max_entries': '1000',
},
}
def __init__(self, config_path: str = None):
"""Initialize the configuration manager.
Args:
config_path: Path to the configuration file. Defaults to autarch_settings.conf
in the framework directory.
"""
if config_path is None:
from core.paths import get_config_path
self.config_path = get_config_path()
else:
self.config_path = Path(config_path)
self.config = configparser.ConfigParser()
self._load_or_create()
def _load_or_create(self):
"""Load existing config or create with defaults."""
if self.config_path.exists():
self.config.read(self.config_path)
self._apply_missing_defaults()
else:
self._create_default_config()
def _apply_missing_defaults(self):
"""Add any missing sections/keys from DEFAULT_CONFIG to the loaded config."""
changed = False
for section, options in self.DEFAULT_CONFIG.items():
if section not in self.config:
self.config[section] = options
changed = True
else:
for key, value in options.items():
if key not in self.config[section]:
self.config[section][key] = value
changed = True
if changed:
self.save()
def _create_default_config(self):
"""Create a default configuration file."""
for section, options in self.DEFAULT_CONFIG.items():
self.config[section] = options
self.save()
def save(self):
"""Save the current configuration to file."""
with open(self.config_path, 'w') as f:
self.config.write(f)
def get(self, section: str, key: str, fallback=None):
"""Get a configuration value.
Args:
section: Configuration section name
key: Configuration key name
fallback: Default value if key doesn't exist
Returns:
The configuration value or fallback
"""
value = self.config.get(section, key, fallback=fallback)
# Strip quotes from values (handles paths with spaces that were quoted)
if value and isinstance(value, str):
value = value.strip().strip('"').strip("'")
return value
def get_int(self, section: str, key: str, fallback: int = 0) -> int:
"""Get a configuration value as integer."""
return self.config.getint(section, key, fallback=fallback)
def get_float(self, section: str, key: str, fallback: float = 0.0) -> float:
"""Get a configuration value as float."""
return self.config.getfloat(section, key, fallback=fallback)
def get_bool(self, section: str, key: str, fallback: bool = False) -> bool:
"""Get a configuration value as boolean."""
return self.config.getboolean(section, key, fallback=fallback)
def set(self, section: str, key: str, value):
"""Set a configuration value.
Args:
section: Configuration section name
key: Configuration key name
value: Value to set
"""
if section not in self.config:
self.config[section] = {}
self.config[section][key] = str(value)
def is_first_run(self) -> bool:
"""Check if this is the first run of AUTARCH."""
return self.get_bool('autarch', 'first_run', fallback=True)
def mark_setup_complete(self):
"""Mark the first-time setup as complete."""
self.set('autarch', 'first_run', 'false')
self.save()
def get_llama_settings(self) -> dict:
"""Get all llama.cpp settings as a dictionary.
Returns:
Dictionary with llama.cpp settings properly typed
"""
return {
'model_path': self.get('llama', 'model_path', ''),
'n_ctx': self.get_int('llama', 'n_ctx', 4096),
'n_threads': self.get_int('llama', 'n_threads', 4),
'n_gpu_layers': self.get_int('llama', 'n_gpu_layers', 0),
'gpu_backend': self.get('llama', 'gpu_backend', 'cpu'),
'temperature': self.get_float('llama', 'temperature', 0.7),
'top_p': self.get_float('llama', 'top_p', 0.9),
'top_k': self.get_int('llama', 'top_k', 40),
'repeat_penalty': self.get_float('llama', 'repeat_penalty', 1.1),
'max_tokens': self.get_int('llama', 'max_tokens', 2048),
'seed': self.get_int('llama', 'seed', -1),
}
def get_osint_settings(self) -> dict:
"""Get all OSINT settings as a dictionary.
Returns:
Dictionary with OSINT settings properly typed
"""
return {
'max_threads': self.get_int('osint', 'max_threads', 8),
'timeout': self.get_int('osint', 'timeout', 8),
'include_nsfw': self.get_bool('osint', 'include_nsfw', False),
}
def get_pentest_settings(self) -> dict:
"""Get all pentest pipeline settings as a dictionary.
Returns:
Dictionary with pentest settings properly typed
"""
return {
'max_pipeline_steps': self.get_int('pentest', 'max_pipeline_steps', 50),
'output_chunk_size': self.get_int('pentest', 'output_chunk_size', 2000),
'auto_execute': self.get_bool('pentest', 'auto_execute', False),
'save_raw_output': self.get_bool('pentest', 'save_raw_output', True),
}
def _get_secret(self, key: str, fallback_section: str = '', fallback_key: str = 'api_key') -> str:
"""Get a secret from the vault, falling back to plaintext config.
This allows a gradual migration: existing plaintext keys still work,
but new keys go to the vault. When a plaintext key is found, it's
automatically migrated to the vault on next save.
"""
try:
from core.vault import get_vault
vault = get_vault()
val = vault.get(key, '')
if val:
return val
except Exception:
pass
# Fallback to plaintext config
if fallback_section:
return self.get(fallback_section, fallback_key, '')
return ''
def _set_secret(self, key: str, value: str, config_section: str = '', config_key: str = 'api_key'):
"""Store a secret in the vault and clear the plaintext config value."""
if not value:
return
try:
from core.vault import get_vault
vault = get_vault()
vault.set(key, value)
# Clear plaintext from config file for security
if config_section:
self.set(config_section, config_key, '')
except Exception:
# Vault not available — store in config as before
if config_section:
self.set(config_section, config_key, value)
2026-03-13 15:17:15 -07:00
def get_claude_settings(self) -> dict:
"""Get all Claude API settings as a dictionary.
Returns:
Dictionary with Claude API settings properly typed
"""
return {
'api_key': self._get_secret('claude_api_key', 'claude', 'api_key'),
2026-03-13 15:17:15 -07:00
'model': self.get('claude', 'model', 'claude-sonnet-4-20250514'),
'max_tokens': self.get_int('claude', 'max_tokens', 4096),
'temperature': self.get_float('claude', 'temperature', 0.7),
}
def get_transformers_settings(self) -> dict:
"""Get all transformers/safetensors settings as a dictionary.
Returns:
Dictionary with transformers settings properly typed
"""
return {
'model_path': self.get('transformers', 'model_path', ''),
'device': self.get('transformers', 'device', 'auto'),
'torch_dtype': self.get('transformers', 'torch_dtype', 'auto'),
'load_in_8bit': self.get_bool('transformers', 'load_in_8bit', False),
'load_in_4bit': self.get_bool('transformers', 'load_in_4bit', False),
'llm_int8_enable_fp32_cpu_offload': self.get_bool('transformers', 'llm_int8_enable_fp32_cpu_offload', False),
'device_map': self.get('transformers', 'device_map', 'auto'),
'trust_remote_code': self.get_bool('transformers', 'trust_remote_code', False),
'max_tokens': self.get_int('transformers', 'max_tokens', 2048),
'temperature': self.get_float('transformers', 'temperature', 0.7),
'top_p': self.get_float('transformers', 'top_p', 0.9),
'top_k': self.get_int('transformers', 'top_k', 40),
'repetition_penalty': self.get_float('transformers', 'repetition_penalty', 1.1),
}
def get_huggingface_settings(self) -> dict:
"""Get all HuggingFace Inference API settings as a dictionary."""
return {
'api_key': self._get_secret('huggingface_api_key', 'huggingface', 'api_key'),
2026-03-13 15:17:15 -07:00
'model': self.get('huggingface', 'model', 'mistralai/Mistral-7B-Instruct-v0.3'),
'endpoint': self.get('huggingface', 'endpoint', ''),
'provider': self.get('huggingface', 'provider', 'auto'),
'max_tokens': self.get_int('huggingface', 'max_tokens', 1024),
'temperature': self.get_float('huggingface', 'temperature', 0.7),
'top_p': self.get_float('huggingface', 'top_p', 0.9),
'top_k': self.get_int('huggingface', 'top_k', 40),
'repetition_penalty': self.get_float('huggingface', 'repetition_penalty', 1.1),
'do_sample': self.get_bool('huggingface', 'do_sample', True),
'seed': self.get_int('huggingface', 'seed', -1),
'stop_sequences': self.get('huggingface', 'stop_sequences', ''),
}
def get_openai_settings(self) -> dict:
"""Get all OpenAI API settings as a dictionary."""
return {
'api_key': self._get_secret('openai_api_key', 'openai', 'api_key'),
2026-03-13 15:17:15 -07:00
'base_url': self.get('openai', 'base_url', 'https://api.openai.com/v1'),
'model': self.get('openai', 'model', 'gpt-4o'),
'max_tokens': self.get_int('openai', 'max_tokens', 4096),
'temperature': self.get_float('openai', 'temperature', 0.7),
'top_p': self.get_float('openai', 'top_p', 1.0),
'frequency_penalty': self.get_float('openai', 'frequency_penalty', 0.0),
'presence_penalty': self.get_float('openai', 'presence_penalty', 0.0),
}
def get_rsf_settings(self) -> dict:
"""Get all RouterSploit settings as a dictionary.
Returns:
Dictionary with RSF settings properly typed
"""
return {
'install_path': self.get('rsf', 'install_path', ''),
'enabled': self.get_bool('rsf', 'enabled', True),
'default_target': self.get('rsf', 'default_target', ''),
'default_port': self.get('rsf', 'default_port', '80'),
'execution_timeout': self.get_int('rsf', 'execution_timeout', 120),
}
def get_upnp_settings(self) -> dict:
"""Get all UPnP settings as a dictionary."""
return {
'enabled': self.get_bool('upnp', 'enabled', True),
'internal_ip': self.get('upnp', 'internal_ip', '10.0.0.26'),
'refresh_hours': self.get_int('upnp', 'refresh_hours', 12),
'mappings': self.get('upnp', 'mappings', ''),
}
def get_mcp_settings(self) -> dict:
"""Get all MCP server settings as a dictionary."""
return {
'enabled': self.get_bool('mcp', 'enabled', False),
'auto_start': self.get_bool('mcp', 'auto_start', False),
'transport': self.get('mcp', 'transport', 'sse'),
'host': self.get('mcp', 'host', '0.0.0.0'),
'port': self.get_int('mcp', 'port', 8081),
'log_level': self.get('mcp', 'log_level', 'INFO'),
'instructions': self.get('mcp', 'instructions', 'AUTARCH security framework tools'),
'auth_enabled': self.get_bool('mcp', 'auth_enabled', False),
'auth_token': self.get('mcp', 'auth_token', ''),
'rate_limit': self.get('mcp', 'rate_limit', ''),
'mask_errors': self.get_bool('mcp', 'mask_errors', False),
'request_timeout': self.get_int('mcp', 'request_timeout', 120),
'max_message_size': self.get_int('mcp', 'max_message_size', 10485760),
'cors_origins': self.get('mcp', 'cors_origins', '*'),
'ssl_enabled': self.get_bool('mcp', 'ssl_enabled', False),
'ssl_cert': self.get('mcp', 'ssl_cert', ''),
'ssl_key': self.get('mcp', 'ssl_key', ''),
'disabled_tools': self.get('mcp', 'disabled_tools', ''),
'nmap_timeout': self.get_int('mcp', 'nmap_timeout', 120),
'tcpdump_timeout': self.get_int('mcp', 'tcpdump_timeout', 30),
'whois_timeout': self.get_int('mcp', 'whois_timeout', 15),
'dns_timeout': self.get_int('mcp', 'dns_timeout', 10),
'geoip_timeout': self.get_int('mcp', 'geoip_timeout', 10),
'geoip_endpoint': self.get('mcp', 'geoip_endpoint', 'http://ip-api.com/json/'),
}
2026-03-13 15:17:15 -07:00
def get_revshell_settings(self) -> dict:
"""Get all reverse shell settings as a dictionary."""
return {
'enabled': self.get_bool('revshell', 'enabled', True),
'host': self.get('revshell', 'host', '0.0.0.0'),
'port': self.get_int('revshell', 'port', 17322),
'auto_start': self.get_bool('revshell', 'auto_start', False),
}
def get_mcp_settings(self) -> dict:
"""Get MCP server settings."""
return {
'enabled': self.get_bool('mcp', 'enabled', False),
'auto_start': self.get_bool('mcp', 'auto_start', False),
'transport': self.get('mcp', 'transport', 'sse'),
'host': self.get('mcp', 'host', '0.0.0.0'),
'port': self.get_int('mcp', 'port', 8081),
'log_level': self.get('mcp', 'log_level', 'INFO'),
'instructions': self.get('mcp', 'instructions', 'AUTARCH security framework tools'),
'auth_enabled': self.get_bool('mcp', 'auth_enabled', False),
'auth_token': self.get('mcp', 'auth_token', ''),
'rate_limit': self.get('mcp', 'rate_limit', ''),
'mask_errors': self.get_bool('mcp', 'mask_errors', False),
'request_timeout': self.get_int('mcp', 'request_timeout', 120),
'max_message_size': self.get_int('mcp', 'max_message_size', 10485760),
'cors_origins': self.get('mcp', 'cors_origins', '*'),
'ssl_enabled': self.get_bool('mcp', 'ssl_enabled', False),
'ssl_cert': self.get('mcp', 'ssl_cert', ''),
'ssl_key': self.get('mcp', 'ssl_key', ''),
'disabled_tools': self.get('mcp', 'disabled_tools', ''),
'nmap_timeout': self.get_int('mcp', 'nmap_timeout', 120),
'tcpdump_timeout': self.get_int('mcp', 'tcpdump_timeout', 30),
'whois_timeout': self.get_int('mcp', 'whois_timeout', 15),
'dns_timeout': self.get_int('mcp', 'dns_timeout', 10),
'geoip_timeout': self.get_int('mcp', 'geoip_timeout', 10),
'geoip_endpoint': self.get('mcp', 'geoip_endpoint', 'http://ip-api.com/json/'),
}
2026-03-13 15:17:15 -07:00
def get_tier_settings(self, tier: str) -> dict:
"""Get settings for a model tier (slm, sam, lam)."""
return {
'enabled': self.get_bool(tier, 'enabled', True),
'backend': self.get(tier, 'backend', 'local'),
'model_path': self.get(tier, 'model_path', ''),
'n_ctx': self.get_int(tier, 'n_ctx', 2048),
'n_gpu_layers': self.get_int(tier, 'n_gpu_layers', -1),
'n_threads': self.get_int(tier, 'n_threads', 4),
}
def get_slm_settings(self) -> dict:
"""Get Small Language Model tier settings."""
return self.get_tier_settings('slm')
def get_sam_settings(self) -> dict:
"""Get Small Action Model tier settings."""
return self.get_tier_settings('sam')
def get_lam_settings(self) -> dict:
"""Get Large Action Model tier settings."""
return self.get_tier_settings('lam')
def get_autonomy_settings(self) -> dict:
"""Get autonomy daemon settings."""
return {
'enabled': self.get_bool('autonomy', 'enabled', False),
'monitor_interval': self.get_int('autonomy', 'monitor_interval', 3),
'rule_eval_interval': self.get_int('autonomy', 'rule_eval_interval', 5),
'max_concurrent_agents': self.get_int('autonomy', 'max_concurrent_agents', 3),
'threat_threshold_auto_respond': self.get_int('autonomy', 'threat_threshold_auto_respond', 40),
'log_max_entries': self.get_int('autonomy', 'log_max_entries', 1000),
}
def get_agents_settings(self) -> dict:
"""Get agent configuration settings."""
return {
'backend': self.get('agents', 'backend', 'local'),
'local_max_steps': self.get_int('agents', 'local_max_steps', 20),
'local_verbose': self.get_bool('agents', 'local_verbose', True),
'claude_enabled': self.get_bool('agents', 'claude_enabled', False),
'claude_model': self.get('agents', 'claude_model', 'claude-sonnet-4-6'),
'claude_max_tokens': self.get_int('agents', 'claude_max_tokens', 16384),
'claude_max_steps': self.get_int('agents', 'claude_max_steps', 30),
'openai_enabled': self.get_bool('agents', 'openai_enabled', False),
'openai_model': self.get('agents', 'openai_model', 'gpt-4o'),
'openai_base_url': self.get('agents', 'openai_base_url', 'https://api.openai.com/v1'),
'openai_max_tokens': self.get_int('agents', 'openai_max_tokens', 16384),
'openai_max_steps': self.get_int('agents', 'openai_max_steps', 30),
}
2026-03-13 15:17:15 -07:00
@staticmethod
def get_templates_dir() -> Path:
"""Get the path to the configuration templates directory."""
from core.paths import get_templates_dir
return get_templates_dir()
@staticmethod
def get_custom_configs_dir() -> Path:
"""Get the path to the custom user configurations directory."""
from core.paths import get_custom_configs_dir
return get_custom_configs_dir()
def list_hardware_templates(self) -> list:
"""List available hardware configuration templates.
Returns:
List of tuples: (template_id, display_name, description, filename)
"""
templates = [
('nvidia_4070_mobile', 'NVIDIA RTX 4070 Mobile', '8GB VRAM, CUDA, optimal for 7B-13B models', 'nvidia_4070_mobile.conf'),
('amd_rx6700xt', 'AMD Radeon RX 6700 XT', '12GB VRAM, ROCm, optimal for 7B-13B models', 'amd_rx6700xt.conf'),
('orangepi5plus_cpu', 'Orange Pi 5 Plus (CPU)', 'RK3588 ARM64, CPU-only, for quantized models', 'orangepi5plus_cpu.conf'),
('orangepi5plus_mali', 'Orange Pi 5 Plus (Mali GPU)', 'EXPERIMENTAL - Mali-G610 OpenCL acceleration', 'orangepi5plus_mali.conf'),
]
return templates
def list_custom_configs(self) -> list:
"""List user-saved custom configurations.
Returns:
List of tuples: (name, filepath)
"""
custom_dir = self.get_custom_configs_dir()
configs = []
for conf_file in custom_dir.glob('*.conf'):
name = conf_file.stem.replace('_', ' ').title()
configs.append((name, conf_file))
return configs
def load_template(self, template_id: str) -> bool:
"""Load a hardware template into the current configuration.
Args:
template_id: The template identifier (e.g., 'nvidia_4070_mobile')
Returns:
True if loaded successfully, False otherwise
"""
templates = {t[0]: t[3] for t in self.list_hardware_templates()}
if template_id not in templates:
return False
template_path = self.get_templates_dir() / templates[template_id]
if not template_path.exists():
return False
return self._load_llm_settings_from_file(template_path)
def load_custom_config(self, filepath: Path) -> bool:
"""Load a custom configuration file.
Args:
filepath: Path to the custom configuration file
Returns:
True if loaded successfully, False otherwise
"""
if not filepath.exists():
return False
return self._load_llm_settings_from_file(filepath)
def _load_llm_settings_from_file(self, filepath: Path) -> bool:
"""Load LLM settings (llama and transformers sections) from a file.
Preserves model_path from current config (doesn't overwrite).
Args:
filepath: Path to the configuration file
Returns:
True if loaded successfully, False otherwise
"""
try:
template_config = configparser.ConfigParser()
template_config.read(filepath)
# Preserve current model paths
current_llama_path = self.get('llama', 'model_path', '')
current_transformers_path = self.get('transformers', 'model_path', '')
# Load llama section
if 'llama' in template_config:
for key, value in template_config['llama'].items():
if key != 'model_path': # Preserve current model path
self.set('llama', key, value)
# Restore model path
if current_llama_path:
self.set('llama', 'model_path', current_llama_path)
# Load transformers section
if 'transformers' in template_config:
for key, value in template_config['transformers'].items():
if key != 'model_path': # Preserve current model path
self.set('transformers', key, value)
# Restore model path
if current_transformers_path:
self.set('transformers', 'model_path', current_transformers_path)
self.save()
return True
except Exception:
return False
def save_custom_config(self, name: str) -> Path:
"""Save current LLM settings to a custom configuration file.
Args:
name: Name for the custom configuration (will be sanitized)
Returns:
Path to the saved configuration file
"""
# Sanitize name for filename
safe_name = ''.join(c if c.isalnum() or c in '-_' else '_' for c in name.lower())
safe_name = safe_name.strip('_')
if not safe_name:
safe_name = 'custom_config'
custom_dir = self.get_custom_configs_dir()
filepath = custom_dir / f'{safe_name}.conf'
# Create config with just LLM settings
custom_config = configparser.ConfigParser()
# Save llama settings
custom_config['llama'] = {}
for key in self.DEFAULT_CONFIG['llama'].keys():
value = self.get('llama', key, '')
if value:
custom_config['llama'][key] = str(value)
# Save transformers settings
custom_config['transformers'] = {}
for key in self.DEFAULT_CONFIG['transformers'].keys():
value = self.get('transformers', key, '')
if value:
custom_config['transformers'][key] = str(value)
# Add header comment
with open(filepath, 'w') as f:
f.write(f'# AUTARCH Custom LLM Configuration\n')
f.write(f'# Name: {name}\n')
f.write(f'# Saved: {Path(self.config_path).name}\n')
f.write('#\n\n')
custom_config.write(f)
return filepath
def delete_custom_config(self, filepath: Path) -> bool:
"""Delete a custom configuration file.
Args:
filepath: Path to the custom configuration file
Returns:
True if deleted successfully, False otherwise
"""
try:
if filepath.exists() and filepath.parent == self.get_custom_configs_dir():
filepath.unlink()
return True
except Exception:
pass
return False
# Global config instance
_config = None
def get_config() -> Config:
"""Get the global configuration instance."""
global _config
if _config is None:
_config = Config()
return _config