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:
69
src/credential_store.py
Normal file
69
src/credential_store.py
Normal 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))))
|
||||
Reference in New Issue
Block a user