fix: Properly initialize ~/.aisbf/ directory with all config and prompt files

- Copy markdown prompt files (*.md) to ~/.aisbf/ on first run
- Fix installation location search order (user-local first, then system)
- Add JSON validation and proper error handling for all config files
- Fix invalid JSON syntax in default rotations.json
- Existing files are never overwritten
- Config files are properly copied from ~/.local/share/aisbf, /usr/local/share/aisbf, /usr/share/aisbf or source directory depending on installation type
parent e7dc89fa
......@@ -346,10 +346,14 @@ class Config:
if (self._custom_config_dir / 'providers.json').exists():
return self._custom_config_dir
# Try installed location first
# Try installed locations in order of preference
# 1. User-local installation (pip install --user)
# 2. System-wide installation (sudo pip install)
# 3. Alternative system location
installed_dirs = [
Path('/usr/share/aisbf'),
Path.home() / '.local' / 'share' / 'aisbf',
Path('/usr/local/share/aisbf'),
Path('/usr/share/aisbf'),
]
for installed_dir in installed_dirs:
......@@ -392,6 +396,15 @@ class Config:
shutil.copy2(src, dst)
print(f"Created default config file: {dst}")
# Copy markdown prompt files if they don't exist
for prompt_file in ['condensation_conversational.md', 'condensation_semantic.md', 'autoselect.md']:
src = source_dir / prompt_file
dst = config_dir / prompt_file
if not dst.exists() and src.exists():
shutil.copy2(src, dst)
print(f"Created default prompt file: {dst}")
def _load_providers(self):
import logging
logger = logging.getLogger(__name__)
......@@ -412,13 +425,31 @@ class Config:
raise FileNotFoundError("Could not find providers.json configuration file")
logger.info(f"Loading providers from: {providers_path}")
try:
with open(providers_path) as f:
data = json.load(f)
# Validate JSON structure
if not data or 'providers' not in data:
logger.error(f"Invalid providers.json: missing 'providers' key")
raise ValueError("Invalid providers.json: missing 'providers' key")
if not isinstance(data['providers'], dict):
logger.error(f"Invalid providers.json: 'providers' must be a dictionary")
raise ValueError("Invalid providers.json: 'providers' must be a dictionary")
self.providers = {k: ProviderConfig(**v) for k, v in data['providers'].items()}
self._loaded_files['providers'] = str(providers_path.absolute())
logger.info(f"Loaded {len(self.providers)} providers: {list(self.providers.keys())}")
for provider_id, provider_config in self.providers.items():
logger.info(f" - {provider_id}: type={provider_config.type}, endpoint={provider_config.endpoint}")
except json.JSONDecodeError as e:
logger.error(f"Failed to parse providers.json: {e}")
raise ValueError(f"Invalid JSON in providers.json: {e}")
except Exception as e:
logger.error(f"Failed to load providers.json: {e}")
raise
logger.info(f"=== Config._load_providers END ===")
def _load_rotations(self):
......@@ -441,8 +472,19 @@ class Config:
raise FileNotFoundError("Could not find rotations.json configuration file")
logger.info(f"Loading rotations from: {rotations_path}")
try:
with open(rotations_path) as f:
data = json.load(f)
# Validate JSON structure
if not data or 'rotations' not in data:
logger.error(f"Invalid rotations.json: missing 'rotations' key")
raise ValueError("Invalid rotations.json: missing 'rotations' key")
if not isinstance(data['rotations'], dict):
logger.error(f"Invalid rotations.json: 'rotations' must be a dictionary")
raise ValueError("Invalid rotations.json: 'rotations' must be a dictionary")
self._loaded_files['rotations'] = str(rotations_path.absolute())
# Extract global notifyerrors setting (top-level, outside rotations)
......@@ -480,6 +522,12 @@ class Config:
logger.warning(f"!!! END WARNING !!!")
else:
logger.info(f" ✓ Provider '{provider_id}' is available")
except json.JSONDecodeError as e:
logger.error(f"Failed to parse rotations.json: {e}")
raise ValueError(f"Invalid JSON in rotations.json: {e}")
except Exception as e:
logger.error(f"Failed to load rotations.json: {e}")
raise
logger.info(f"=== Config._load_rotations END ===")
......@@ -504,14 +552,27 @@ class Config:
raise FileNotFoundError("Could not find autoselect.json configuration file")
logger.info(f"Loading autoselect from: {autoselect_path}")
try:
with open(autoselect_path) as f:
data = json.load(f)
# Validate JSON structure
if not data:
logger.error(f"Invalid autoselect.json: empty file")
raise ValueError("Invalid autoselect.json: empty file")
self.autoselect = {k: AutoselectConfig(**v) for k, v in data.items()}
self._loaded_files['autoselect'] = str(autoselect_path.absolute())
logger.info(f"Loaded {len(self.autoselect)} autoselect configurations: {list(self.autoselect.keys())}")
# Build and cache model embeddings for semantic matching
self._build_model_embeddings()
except json.JSONDecodeError as e:
logger.error(f"Failed to parse autoselect.json: {e}")
raise ValueError(f"Invalid JSON in autoselect.json: {e}")
except Exception as e:
logger.error(f"Failed to load autoselect.json: {e}")
raise
logger.info(f"=== Config._load_autoselect END ===")
......
......@@ -2,5 +2,4 @@
"notifyerrors": false,
"rotations": {
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment