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
95 lines
2.5 KiB
Python
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()
|