rlabs-agentguard

0.3.2 Not latest — view latest

A quality-assurance engine for LLM-generated code

License
Unknown license
Published
February 25, 2026
14d ago
Package Registry
README badge Customize →
License Sources
SourceLicenseClass
Licensie (detected)
Pending-
PyPI (reported)
Not reported-

License detection is still in progress for this version.

Loading dependencies…
License File
"""License cache — local 24-hour cache for marketplace license checks.

Avoids hitting the platform API on every archetype load.  Entries are
stored as JSON in ``~/.agentguard/cache/licenses.json``.
"""

from __future__ import annotations

import json
import logging
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)

# Default cache location
CACHE_DIR = Path.home() / ".agentguard" / "cache"
CACHE_FILE = CACHE_DIR / "licenses.json"

# Default TTL: 24 hours
DEFAULT_TTL_SECONDS = 86_400


@dataclass
class LicenseEntry:
    """A single cached license result."""

    slug: str
    licensed: bool
    reason: str  # "free", "purchased", "author", "not_purchased"
    checked_at: float  # epoch seconds

    def is_expired(self, ttl: float = DEFAULT_TTL_SECONDS) -> bool:
        """True if the entry is older than *ttl* seconds."""
        return (time.time() - self.checked_at) > ttl

    def to_dict(self) -> dict[str, Any]:
        return {
            "slug": self.slug,
            "licensed": self.licensed,
            "reason": self.reason,
            "checked_at": self.checked_at,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> LicenseEntry:
        return cls(
            slug=data["slug"],
            licensed=data["licensed"],
            reason=data.get("reason", "unknown"),
            checked_at=data.get("checked_at", 0.0),
        )


class LicenseCache:
    """Persistent file-backed license cache with configurable TTL.

    Usage::

        cache = LicenseCache()
        entry = cache.get("my-archetype")
        if entry is None:
            # Call platform API …
            cache.set("my-archetype", licensed=True, reason="purchased")

    Args:
        cache_file: Path to the JSON cache file.
        ttl: Time-to-live in seconds (default 24h).
    """

    def __init__(
        self,
        cache_file: Path | None = None,
        ttl: float = DEFAULT_TTL_SECONDS,
    ) -> None:
        self._file = cache_file or CACHE_FILE
        self._ttl = ttl
        self._entries: dict[str, LicenseEntry] = {}
        self._loaded = False

    def get(self, slug: str) -> LicenseEntry | None:
        """Return a valid (non-expired) cache entry, or ``None``."""
        self._ensure_loaded()
        entry = self._entries.get(slug)
        if entry is None or entry.is_expired(self._ttl):
            return None
        return entry

    def set(self, slug: str, *, licensed: bool, reason: str = "") -> None:
        """Store a license check result."""
        self._ensure_loaded()
        self._entries[slug] = LicenseEntry(
            slug=slug,
            licensed=licensed,
            reason=reason,
            checked_at=time.time(),
        )
        self._persist()

    def remove(self, slug: str) -> None:
        """Remove a single entry from the cache."""
        self._ensure_loaded()
        self._entries.pop(slug, None)
        self._persist()

    def clear(self) -> None:
        """Remove all cached entries."""
        self._entries.clear()
        self._persist()

    def list_entries(self) -> list[LicenseEntry]:
        """Return all non-expired entries."""
        self._ensure_loaded()
        return [e for e in self._entries.values() if not e.is_expired(self._ttl)]

    # ── Persistence ───────────────────────────────────────────────

    def _ensure_loaded(self) -> None:
        if self._loaded:
            return
        self._loaded = True
        if not self._file.exists():
            return
        try:
            data = json.loads(self._file.read_text(encoding="utf-8"))
            for item in data.get("licenses", []):
                entry = LicenseEntry.from_dict(item)
                self._entries[entry.slug] = entry
        except Exception:
            logger.debug("Failed to load license cache from %s", self._file, exc_info=True)

    def _persist(self) -> None:
        try:
            self._file.parent.mkdir(parents=True, exist_ok=True)
            payload = {
                "licenses": [e.to_dict() for e in self._entries.values()],
            }
            self._file.write_text(
                json.dumps(payload, indent=2),
                encoding="utf-8",
            )
        except Exception:
            logger.debug("Failed to persist license cache to %s", self._file, exc_info=True)
Versions
7 versions
VersionLicensePublishedStatus
0.5.0 Latest MITMar 9, 2026 Scanned
0.4.0 MITMar 7, 2026 Scanned
0.3.2 Viewing-Feb 25, 2026 Pending
0.3.1 -Feb 24, 2026 Pending
0.3.0 -Feb 23, 2026 Pending
0.2.0 -Feb 22, 2026 Pending
0.1.0 MITFeb 21, 2026 Scanned