Files
ROMFASTSQL/proxmox/lxc171-claude-agent/discord-bridge/config.py
Claude Agent ddbe9de4bc fix(discord-bridge): director de lucru dedicat + log nedublat
- DEFAULT_CWD trece de la /workspace la /workspace/claude-agent, un spatiu cu git
  propriu, ca fisierele facute din Discord sa aiba istoric separat de proiectele
  reale. `!cd <cale>` ramane disponibil oriunde in /workspace, deci decizia din
  plan (fara allowlist de proiecte) nu se schimba.
- setup_logging: sub systemd unitul redirecteaza deja stdout in bot.log
  (StandardOutput=append:), iar FileHandler-ul scria fiecare linie a doua oara
  in acelasi fisier. FileHandler ramane doar la rulare manuala.

Verificat in productie: bot conectat, un tur real incheiat curat (inflight null,
cost $0.0742 contabilizat), restart fara orfani. Suita: 275 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 11:30:31 +00:00

124 lines
3.7 KiB
Python

"""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()