yes-claudio

0.1.2

Auto-approve Claude Code permission prompts with a denylist and daily limits

License
Unknown license
Published
May 25, 2026
2h 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
import hashlib
import json
import os
import uuid
from datetime import datetime, timedelta, timezone
from pathlib import Path
from urllib import error, parse, request

PRODUCT_ID = os.environ.get("AUTOCONFIRM_PRODUCT_ID", "QnfHW51bAD808l1E6zbd7w==")
GUMROAD_URL = "https://api.gumroad.com/v2/licenses/verify"
GRACE_DAYS = 7
MAX_ACTIVATIONS = 2


def machine_id() -> str:
    return hashlib.sha256(str(uuid.getnode()).encode()).hexdigest()[:16]


def _verify_online(license_key: str) -> dict:
    if not PRODUCT_ID:
        return {}
    data = parse.urlencode({
        "product_id": PRODUCT_ID,
        "license_key": license_key.strip().upper(),
        "increment_uses_count": "false",
    }).encode()
    req = request.Request(GUMROAD_URL, data=data, method="POST")
    req.add_header("Content-Type", "application/x-www-form-urlencoded")
    try:
        with request.urlopen(req, timeout=8) as resp:
            return json.loads(resp.read())
    except (error.URLError, error.HTTPError, json.JSONDecodeError, OSError):
        return {}


def activate(license_key: str, config_dir: Path) -> tuple[bool, str]:
    """Verifica serial na Gumroad e grava token local. Retorna (sucesso, mensagem)."""
    if not PRODUCT_ID:
        return False, "product_id não configurado (aguardando conta Gumroad)."

    result = _verify_online(license_key)
    if not result.get("success"):
        msg = result.get("message", "Serial inválida ou não encontrada.")
        return False, msg

    purchase = result.get("purchase", {})
    uses = purchase.get("uses", 1)
    if uses > MAX_ACTIVATIONS:
        return False, f"Serial já ativada em {uses} máquinas (máximo: {MAX_ACTIVATIONS})."

    token = {
        "license_key": license_key.strip().upper(),
        "valid": True,
        "email": purchase.get("email", ""),
        "machine_id": machine_id(),
        "last_verified": datetime.now(tz=timezone.utc).isoformat(),
        "uses": uses,
    }
    config_dir.mkdir(parents=True, exist_ok=True)
    (config_dir / "license.json").write_text(
        json.dumps(token, indent=2, ensure_ascii=False), encoding="utf-8"
    )
    return True, f"Licença ativada para {token['email']}."


def check_local(license_file: Path) -> bool:
    """
    Verificação rápida (sem rede) para uso no handler (hot path).
    Válido se: arquivo existe + valid=True + machine_id bate + dentro do período de graça.
    """
    if not license_file.exists():
        return False
    try:
        token = json.loads(license_file.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return False

    if not token.get("valid"):
        return False
    if token.get("machine_id") != machine_id():
        return False

    last_verified_str = token.get("last_verified", "")
    try:
        last_verified = datetime.fromisoformat(last_verified_str)
        age = datetime.now(tz=timezone.utc) - last_verified
        return age <= timedelta(days=GRACE_DAYS)
    except (ValueError, TypeError):
        return False


def refresh(license_file: Path) -> tuple[bool, str]:
    """
    Reverifica online e atualiza last_verified. Chamado pela GUI ao abrir.
    Retorna (válido, mensagem).
    """
    if not license_file.exists():
        return False, "Sem licença local."
    try:
        token = json.loads(license_file.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return False, "Erro ao ler arquivo de licença."

    if token.get("machine_id") != machine_id():
        return False, "Licença não pertence a esta máquina."

    result = _verify_online(token.get("license_key", ""))
    if result.get("success"):
        token["valid"] = True
        token["last_verified"] = datetime.now(tz=timezone.utc).isoformat()
        token["uses"] = result.get("purchase", {}).get("uses", token.get("uses", 1))
        try:
            license_file.write_text(
                json.dumps(token, indent=2, ensure_ascii=False), encoding="utf-8"
            )
        except OSError:
            pass
        return True, "Licença válida."

    token["valid"] = False
    try:
        license_file.write_text(
            json.dumps(token, indent=2, ensure_ascii=False), encoding="utf-8"
        )
    except OSError:
        pass
    return False, result.get("message", "Serial inválida ou revogada.")
Versions
3 versions
VersionLicensePublishedStatus
0.1.2 Latest Viewing-May 25, 2026 Pending
0.1.1 -May 24, 2026 Pending
0.1.0 -May 24, 2026 Pending