"""Configuratie comuna a puntii Discord -> Claude Code. Citeste ~/.claude-discord/env (KEY=value, tolerant la comentarii si ghilimele). Lane B si Lane C importa acest modul pentru cai si setari. """ from __future__ import annotations import os import pathlib # Directorul de baza poate fi mutat in teste prin CLAUDE_DISCORD_DIR. _DEFAULT_DIR = pathlib.Path.home() / ".claude-discord" STATE_DIR: pathlib.Path = pathlib.Path(os.environ.get("CLAUDE_DISCORD_DIR") or _DEFAULT_DIR) APPROVALS_DIR: pathlib.Path = STATE_DIR / "approvals" STATE_FILE: pathlib.Path = STATE_DIR / "state.json" LOG_DIR: pathlib.Path = STATE_DIR / "logs" ENV_FILE: pathlib.Path = STATE_DIR / "env" SETTINGS_FILE: pathlib.Path = STATE_DIR / "bot-settings.json" _env: dict[str, str] = {} # Valori implicite pentru cheile pe care le foloseste nucleul. DEFAULTS: dict[str, str] = { "MODEL_DEFAULT": "sonnet", "COST_CAP_USD_DAY": "10.0", "MAX_PROCS": "4", "TURN_TIMEOUT_S": "900", "IDLE_REAP_S": "1200", "RATE_PER_USER_PER_MIN": "10", "CLAUDE_BIN": "claude", "DEFAULT_CWD": "/workspace/claude-agent", } def parse_env(text: str) -> dict[str, str]: """Parseaza un fisier de tip KEY=value. Nu arunca niciodata.""" out: dict[str, str] = {} for raw in text.splitlines(): line = raw.strip() if not line or line.startswith("#"): continue if line.startswith("export "): line = line[len("export "):].strip() if "=" not in line: continue key, _, val = line.partition("=") key = key.strip() if not key: continue val = val.strip() # taie comentariul de la finalul liniei doar daca valoarea nu e in ghilimele if val[:1] not in ("'", '"'): cut = val.find(" #") if cut >= 0: val = val[:cut].rstrip() if len(val) >= 2 and val[0] == val[-1] and val[0] in ("'", '"'): val = val[1:-1] out[key] = val return out def reload(base_dir: str | os.PathLike | None = None) -> dict[str, str]: """Recalculeaza caile si reciteste env-ul. Returneaza dictionarul incarcat.""" global STATE_DIR, APPROVALS_DIR, STATE_FILE, LOG_DIR, ENV_FILE, SETTINGS_FILE, _env if base_dir is None: base_dir = os.environ.get("CLAUDE_DISCORD_DIR") or _DEFAULT_DIR STATE_DIR = pathlib.Path(base_dir) APPROVALS_DIR = STATE_DIR / "approvals" STATE_FILE = STATE_DIR / "state.json" LOG_DIR = STATE_DIR / "logs" ENV_FILE = STATE_DIR / "env" SETTINGS_FILE = STATE_DIR / "bot-settings.json" try: _env = parse_env(ENV_FILE.read_text(encoding="utf-8")) except OSError: _env = {} return _env def get(key: str, default=None): """Env-ul de pe disc, apoi mediul procesului, apoi DEFAULTS, apoi `default`.""" if key in _env: return _env[key] if key in os.environ: return os.environ[key] if default is not None: return default return DEFAULTS.get(key, default) def get_float(key: str, default: float) -> float: try: return float(get(key, str(default))) except (TypeError, ValueError): return default def get_int(key: str, default: int) -> int: try: return int(float(get(key, str(default)))) except (TypeError, ValueError): return default def get_list(key: str, default=()) -> list[str]: """Lista separata prin virgula sau spatiu (allowlist-uri).""" raw = get(key, "") if not raw: return list(default) return [p for p in raw.replace(",", " ").split() if p] def ensure_dirs() -> None: for d in (STATE_DIR, APPROVALS_DIR, LOG_DIR): try: d.mkdir(parents=True, exist_ok=True) except OSError: pass reload()