feat(browser): auto-commit con 178 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:22:23 +02:00
parent 7d100e7f3e
commit 763e06c127
178 changed files with 19917 additions and 317 deletions
+168
View File
@@ -0,0 +1,168 @@
"""Pipeline: anade UN evento al calendario CalDAV de Enmanuel en una llamada.
Compone funciones del registry:
- build_vevent (core): compone el dict de evento -> texto VCALENDAR.
- extract_or_make_uid (infra): resuelve/sintetiza el UID si no se da.
- pass_get_secret (infra): resuelve la contrasena DAV desde `pass`.
- caldav_put_event (infra): hace el HTTP PUT a la coleccion CalDAV.
Impuro (red + lectura de secreto via subproceso). La contrasena NUNCA se logea
ni aparece en el resultado. Solo stdlib + funciones del registry.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from core.build_vevent import build_vevent
from infra.extract_or_make_uid import extract_or_make_uid
from infra.pass_get_secret import pass_get_secret
from infra.caldav_put_event import caldav_put_event
DEFAULT_BASE_URL = "https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com"
DEFAULT_USERNAME = "enmanuel"
DEFAULT_COLLECTION = "/enmanuel/calendars/calendar/"
def add_event_dav(
summary: str,
start: str,
end: str = "",
*,
location: str = "",
description: str = "",
all_day: bool = False,
rrule: str = "",
alarm_minutes: int = 0,
uid: str = "",
base_url: str = DEFAULT_BASE_URL,
username: str = DEFAULT_USERNAME,
collection_path: str = DEFAULT_COLLECTION,
secret_path: str = "dav/xandikos-enmanuel",
timeout_s: float = 20.0,
verify_tls: bool = True,
) -> dict:
"""Anade un evento al calendario CalDAV de Enmanuel en un solo paso.
Args:
summary: titulo del evento (-> SUMMARY). Obligatorio.
start: fecha/hora de inicio (p.ej. '2026-06-20T17:00'). Obligatorio.
end: fecha/hora de fin. Si vacio y no es all_day, build_vevent deriva +1h.
location: lugar del evento (-> LOCATION).
description: descripcion (-> DESCRIPTION).
all_day: si True, evento de dia completo (DTSTART;VALUE=DATE).
rrule: regla de recurrencia (p.ej. 'FREQ=WEEKLY;BYDAY=MO').
alarm_minutes: si > 0, anade un recordatorio N minutos antes (VALARM).
uid: UID explicito del evento. Si vacio, se sintetiza determinista a
partir del VCALENDAR generado (idempotente: re-subir sobrescribe).
base_url: URL base del servidor DAV. Default = Xandikos de Enmanuel.
username: usuario para HTTP Basic auth. Default 'enmanuel'.
collection_path: ruta de la coleccion CalDAV destino.
secret_path: ruta del secreto en `pass` con la contrasena DAV.
timeout_s: timeout del PUT en segundos.
verify_tls: si True (default) verifica el certificado TLS.
Returns:
dict. En exito: {status: 'ok', http_status: int, uid: str, url: str}.
En error (sin lanzar): {status: 'error', error: str, uid: str|None,
http_status: int|None}. La contrasena NUNCA aparece en el resultado.
"""
event = {
"summary": summary,
"start": start,
"end": end or None,
"location": location or None,
"description": description or None,
"all_day": all_day,
"rrule": rrule or None,
"alarm_minutes": alarm_minutes or None,
"uid": uid or None,
}
try:
vcalendar = build_vevent(event)
except ValueError as e:
return {"status": "error", "error": str(e), "uid": None, "http_status": None}
# UID definitivo: el explicito si vino, o el (sintetico) del VCALENDAR.
final_uid = uid.strip() if uid else extract_or_make_uid(vcalendar, prefix="evt-")
secret = pass_get_secret(secret_path)
if secret.get("status") != "ok":
return {
"status": "error",
"error": "pass: %s" % secret.get("error", "unknown"),
"uid": final_uid,
"http_status": None,
}
password = secret["value"]
res = caldav_put_event(
base_url,
username,
password,
collection_path,
final_uid,
vcalendar,
timeout_s=timeout_s,
verify_tls=verify_tls,
)
# Reusa el dict de caldav_put_event y le anade el uid usado.
out = dict(res)
out["uid"] = final_uid
return out
if __name__ == "__main__":
import argparse
import json
parser = argparse.ArgumentParser(
description="Anade un evento al calendario CalDAV de Enmanuel."
)
parser.add_argument("--summary", required=True, help="Titulo del evento.")
parser.add_argument(
"--start", required=True, help="Inicio, p.ej. 2026-06-20T17:00."
)
parser.add_argument("--end", default="", help="Fin, p.ej. 2026-06-20T18:00.")
parser.add_argument("--location", default="", help="Lugar.")
parser.add_argument("--description", default="", help="Descripcion.")
parser.add_argument(
"--all-day", action="store_true", help="Evento de dia completo."
)
parser.add_argument(
"--rrule", default="", help="Recurrencia, p.ej. FREQ=WEEKLY;BYDAY=MO."
)
parser.add_argument(
"--alarm-minutes", type=int, default=0, help="Recordatorio N min antes."
)
parser.add_argument("--uid", default="", help="UID explicito (opcional).")
parser.add_argument("--base-url", default=DEFAULT_BASE_URL)
parser.add_argument("--username", default=DEFAULT_USERNAME)
parser.add_argument("--collection-path", default=DEFAULT_COLLECTION)
parser.add_argument("--secret-path", default="dav/xandikos-enmanuel")
parser.add_argument("--timeout-s", type=float, default=20.0)
parser.add_argument(
"--no-verify-tls", action="store_true", help="Desactiva verificacion TLS."
)
args = parser.parse_args()
result = add_event_dav(
args.summary,
args.start,
args.end,
location=args.location,
description=args.description,
all_day=args.all_day,
rrule=args.rrule,
alarm_minutes=args.alarm_minutes,
uid=args.uid,
base_url=args.base_url,
username=args.username,
collection_path=args.collection_path,
secret_path=args.secret_path,
timeout_s=args.timeout_s,
verify_tls=not args.no_verify_tls,
)
print(json.dumps(result))