Files
silo/pkg/calc/pythonpath/silo_calc/settings.py
Forbes afb382b68d feat: LibreOffice Calc extension, ODS library, AI description, audit design
Calc extension (pkg/calc/):
- Python UNO ProtocolHandler with 8 toolbar commands
- SiloClient HTTP client adapted from FreeCAD workbench
- Pull BOM/Project: populates sheets with 28-col format, hidden property
  columns, row hash tracking, auto project tagging
- Push: row classification, create/update items, conflict detection
- Completion wizard: 3-step category/description/fields with PN conflict
  resolution dialog
- OpenRouter AI integration: generate standardized descriptions from seller
  text, configurable model/instructions, review dialog
- Settings: JSON persistence, env var fallbacks, OpenRouter fields
- 31 unit tests (no UNO/network required)

Go ODS library (internal/ods/):
- Pure Go ODS read/write (ZIP of XML, no headless LibreOffice)
- Writer, reader, 10 round-trip tests

Server ODS endpoints (internal/api/ods.go):
- GET /api/items/export.ods, template.ods, POST import.ods
- GET /api/items/{pn}/bom/export.ods
- GET /api/projects/{code}/sheet.ods
- POST /api/sheets/diff

Documentation:
- docs/CALC_EXTENSION.md: extension progress report
- docs/COMPONENT_AUDIT.md: web audit tool design with weighted scoring,
  assembly computed fields, batch AI assistance plan
2026-02-01 10:06:20 -06:00

95 lines
2.5 KiB
Python

"""Persistent settings for the Silo Calc extension.
Settings are stored in ``~/.config/silo/calc-settings.json``.
The file is a flat JSON dict with known keys.
"""
import json
import os
from pathlib import Path
from typing import Any, Dict
_SETTINGS_DIR = (
Path(os.environ.get("XDG_CONFIG_HOME", "~/.config")).expanduser() / "silo"
)
_SETTINGS_FILE = _SETTINGS_DIR / "calc-settings.json"
# Default values for every known key.
_DEFAULTS: Dict[str, Any] = {
"api_url": "",
"api_token": "",
"ssl_verify": True,
"ssl_cert_path": "",
"auth_username": "",
"auth_role": "",
"auth_source": "",
"projects_dir": "", # fallback: SILO_PROJECTS_DIR env or ~/projects
"default_schema": "kindred-rd",
"openrouter_api_key": "", # fallback: OPENROUTER_API_KEY env var
"openrouter_model": "", # fallback: ai_client.DEFAULT_MODEL
"openrouter_instructions": "", # fallback: ai_client.DEFAULT_INSTRUCTIONS
}
def load() -> Dict[str, Any]:
"""Load settings, returning defaults for any missing keys."""
cfg = dict(_DEFAULTS)
if _SETTINGS_FILE.is_file():
try:
with open(_SETTINGS_FILE, "r") as f:
stored = json.load(f)
cfg.update(stored)
except (json.JSONDecodeError, OSError):
pass
return cfg
def save(cfg: Dict[str, Any]) -> None:
"""Persist the full settings dict to disk."""
_SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
with open(_SETTINGS_FILE, "w") as f:
json.dump(cfg, f, indent=2)
def get(key: str, default: Any = None) -> Any:
"""Convenience: load a single key."""
cfg = load()
return cfg.get(key, default)
def put(key: str, value: Any) -> None:
"""Convenience: update a single key and persist."""
cfg = load()
cfg[key] = value
save(cfg)
def save_auth(username: str, role: str = "", source: str = "", token: str = "") -> None:
"""Store authentication info."""
cfg = load()
cfg["auth_username"] = username
cfg["auth_role"] = role
cfg["auth_source"] = source
if token:
cfg["api_token"] = token
save(cfg)
def clear_auth() -> None:
"""Remove stored auth credentials."""
cfg = load()
cfg["api_token"] = ""
cfg["auth_username"] = ""
cfg["auth_role"] = ""
cfg["auth_source"] = ""
save(cfg)
def get_projects_dir() -> Path:
"""Return the resolved projects base directory."""
cfg = load()
d = cfg.get("projects_dir", "")
if not d:
d = os.environ.get("SILO_PROJECTS_DIR", "~/projects")
return Path(d).expanduser()