rename secrets.py to credential_store.py, enhance /status, add usage tracking

- Rename src/secrets.py → src/credential_store.py (avoid stdlib conflict)
- Enhanced /status command: uptime, tokens, cost, context window usage
- Session metadata now tracks input/output tokens, cost, duration
- _safe_env() changed from allowlist to blocklist approach
- Better Claude CLI error logging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MoltBot Service
2026-02-13 17:54:59 +00:00
parent 0ecfa630eb
commit 85c72e4b3d
7 changed files with 155 additions and 39 deletions

69
src/credential_store.py Normal file
View File

@@ -0,0 +1,69 @@
"""Echo Core secrets manager — keyring wrapper."""
import json
import keyring
SERVICE = "echo-core"
# Required secrets that should exist for full functionality
REQUIRED_SECRETS = ["discord_token"]
def set_secret(name: str, value: str) -> None:
"""Store a secret in the system keyring."""
keyring.set_password(SERVICE, name, value)
# Track secret name in the registry
names = _get_registry()
if name not in names:
names.append(name)
_save_registry(names)
def get_secret(name: str) -> str | None:
"""Retrieve a secret from keyring. Returns None if not found."""
return keyring.get_password(SERVICE, name)
def get_json_secret(name: str) -> dict | None:
"""Retrieve and JSON-deserialize a secret."""
raw = get_secret(name)
if raw is None:
return None
return json.loads(raw)
def delete_secret(name: str) -> bool:
"""Delete a secret. Returns True if existed."""
try:
keyring.delete_password(SERVICE, name)
except keyring.errors.PasswordDeleteError:
pass
names = _get_registry()
if name in names:
names.remove(name)
_save_registry(names)
return True
return False
def list_secrets() -> list[str]:
"""List all stored secret names."""
return _get_registry()
def check_secrets() -> dict[str, bool]:
"""Check which required secrets exist. Returns {name: exists}."""
return {name: get_secret(name) is not None for name in REQUIRED_SECRETS}
def _get_registry() -> list[str]:
"""Get list of secret names from keyring registry."""
raw = keyring.get_password(SERVICE, "_registry")
if raw is None:
return []
return json.loads(raw)
def _save_registry(names: list[str]) -> None:
"""Save secret names registry to keyring."""
keyring.set_password(SERVICE, "_registry", json.dumps(sorted(set(names))))