docs: apply fleet-template (16-artifact scaffold)
Adds missing standard artifacts: - README.md (if missing) - AGENTS.md (AI agent contract) - PLAN.md (current sprint) - STATUS.md (where we are) - DEVELOPMENT.md (dev workflow) - DEPLOYMENT.md (deploy procedure) - TESTING.md (test strategy) - DECISIONS.md (ADR index + templates) - .github/CODEOWNERS - .github/workflows/ci.yml Preserves all existing artifacts. Refs: RugMunchMedia/fleet-template
This commit is contained in:
commit
47ba268131
310 changed files with 38429 additions and 0 deletions
105
sessions.py
Normal file
105
sessions.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""Pry — persistent browser session management.
|
||||
Saves and restores Playwright browser contexts to disk."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SESSIONS_DIR = Path(os.path.expanduser("~/.pry/sessions"))
|
||||
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def _session_path(session_id: str) -> Path:
|
||||
"""Get filesystem path for a session's saved state."""
|
||||
return SESSIONS_DIR / f"{session_id}.json"
|
||||
|
||||
|
||||
async def save_session(
|
||||
session_id: str,
|
||||
cookies: list[dict[str, Any]],
|
||||
local_storage: dict[str, str] | None = None,
|
||||
session_storage: dict[str, str] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Save browser session state to disk."""
|
||||
data = {
|
||||
"session_id": session_id,
|
||||
"saved_at": datetime.now(UTC).isoformat(),
|
||||
"cookies": cookies,
|
||||
"local_storage": local_storage or {},
|
||||
"session_storage": session_storage or {},
|
||||
"metadata": metadata or {},
|
||||
}
|
||||
path = _session_path(session_id)
|
||||
try:
|
||||
path.write_text(json.dumps(data, indent=2))
|
||||
logger.info("session_saved", extra={"session_id": session_id, "path": str(path)})
|
||||
except OSError:
|
||||
logger.exception("session_save_failed", extra={"session_id": session_id})
|
||||
|
||||
|
||||
async def load_session(session_id: str) -> dict[str, Any] | None:
|
||||
"""Load saved browser session from disk."""
|
||||
path = _session_path(session_id)
|
||||
if not path.exists():
|
||||
logger.info("session_not_found", extra={"session_id": session_id})
|
||||
return None
|
||||
try:
|
||||
data: dict[str, Any] = cast("dict[str, Any]", json.loads(path.read_text()))
|
||||
logger.info(
|
||||
"session_loaded", extra={"session_id": session_id, "saved_at": data.get("saved_at")}
|
||||
)
|
||||
return data
|
||||
except (json.JSONDecodeError, OSError):
|
||||
logger.exception("session_load_failed", extra={"session_id": session_id})
|
||||
return None
|
||||
|
||||
|
||||
async def delete_session(session_id: str) -> bool:
|
||||
"""Delete a saved session."""
|
||||
path = _session_path(session_id)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
logger.info("session_deleted", extra={"session_id": session_id})
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def list_sessions() -> list[dict[str, Any]]:
|
||||
"""List all saved sessions with metadata."""
|
||||
sessions = []
|
||||
for path in sorted(SESSIONS_DIR.glob("*.json"), key=os.path.getmtime, reverse=True):
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
sessions.append(
|
||||
{
|
||||
"session_id": data.get("session_id", path.stem),
|
||||
"saved_at": data.get("saved_at", ""),
|
||||
"metadata": data.get("metadata", {}),
|
||||
"cookie_count": len(data.get("cookies", [])),
|
||||
"size_bytes": path.stat().st_size,
|
||||
}
|
||||
)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
continue
|
||||
return sessions
|
||||
|
||||
|
||||
async def cleanup_sessions(max_age_days: int = 30) -> int:
|
||||
"""Remove sessions older than max_age_days. Returns count removed."""
|
||||
now = time.time()
|
||||
removed = 0
|
||||
for path in SESSIONS_DIR.glob("*.json"):
|
||||
age_days = (now - path.stat().st_mtime) / 86400
|
||||
if age_days > max_age_days:
|
||||
path.unlink()
|
||||
removed += 1
|
||||
if removed:
|
||||
logger.info("session_cleanup", extra={"removed": removed, "max_age_days": max_age_days})
|
||||
return removed
|
||||
Loading…
Add table
Add a link
Reference in a new issue