Auto-approve Claude Code permission prompts with a denylist and daily limits
License Sources
| Source | License | Class |
|---|---|---|
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", "ggsbed")
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.")