from __future__ import annotations import json from dataclasses import asdict, dataclass, field from datetime import datetime, timezone from pathlib import Path @dataclass class TelemetryEvent: """A single structured telemetry event.""" timestamp: str event_type: str target: str details: dict = field(default_factory=dict) def to_dict(self) -> dict: return asdict(self) def _session_log_path(root: Path) -> Path: """Return the path to the session-log.jsonl file under .rdb/.""" rdb_dir = root / ".rdb" rdb_dir.mkdir(parents=True, exist_ok=True) return rdb_dir / "session-log.jsonl" def record_event(root: Path, event_type: str, target: str, details: dict | None = None) -> TelemetryEvent: """Record a telemetry event to the session log. Creates the .rdb/session-log.jsonl file if it does not exist. Each call appends one JSON line to the file. Args: root: Project root directory. event_type: Category of event, e.g. 'command', 'task', 'guardrail'. target: The entity the event relates to, e.g. 'rdb prompt'. details: Optional extra key-value pairs for context. Returns: The TelemetryEvent that was recorded. """ event = TelemetryEvent( timestamp=datetime.now(timezone.utc).isoformat(), event_type=event_type, target=target, details=details or {}, ) log_path = _session_log_path(root) with open(log_path, "a", encoding="utf-8") as f: f.write(json.dumps(event.to_dict()) + "\n") return event def read_events(root: Path) -> list[TelemetryEvent]: """Read all events from the session log. Returns an empty list if the log does not yet exist or is empty. Malformed lines are silently skipped. """ log_path = _session_log_path(root) if not log_path.exists(): return [] events: list[TelemetryEvent] = [] for line in log_path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line: continue try: data = json.loads(line) events.append(TelemetryEvent(**data)) except (json.JSONDecodeError, TypeError): pass return events