Files
lucianoandClaude Fable 5.1 ff79832c30 AvatarPy: assistente personale con volto 3D, voce, memoria e plugin
Riscrittura in Python dell'assistente Avatar con interfaccia HUD (derivata da Mark LIV, CC BY-NC 4.0, vedi NOTICE.md).
Tre motori (Claude API, server locale OpenAI-compatibile, Claude Code), voce Kokoro/macOS, Whisper MLX,
avatar 3D con sincronizzazione labiale, memoria per categorie, allegati con OCR, monitor con avvisi,
plugin per Calendario, Mail, Promemoria, Note, Musica, app, Mac, timer, meteo, contatti, Messaggi,
file, Comandi Rapidi, browser, Telegram, WhatsApp (archivio e tempo reale).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 16:21:39 +02:00

150 lines
6.1 KiB
Python

"""Promemoria di macOS: elenca, aggiunge e completa promemoria (EventKit, sincronizzati con iPhone)."""
from __future__ import annotations
import threading
from datetime import datetime, timedelta
_store = None
_lock = threading.Lock()
def _get_store():
global _store
with _lock:
if _store is not None:
return _store
import EventKit
store = EventKit.EKEventStore.alloc().init()
done, res = threading.Event(), {}
def cb(granted, err):
res["granted"] = bool(granted)
done.set()
if hasattr(store, "requestFullAccessToRemindersWithCompletion_"):
store.requestFullAccessToRemindersWithCompletion_(cb)
else:
store.requestAccessToEntityType_completion_(EventKit.EKEntityTypeReminder, cb)
done.wait(60)
if not res.get("granted"):
raise RuntimeError("Accesso ai Promemoria negato: concedilo in Impostazioni di Sistema > Privacy e sicurezza > Promemoria.")
_store = store
return store
def _parse_dt(s: str) -> datetime | None:
s = (s or "").strip().replace("T", " ")
if not s:
return None
for fmt in ("%Y-%m-%d %H:%M", "%Y-%m-%d"):
try:
return datetime.strptime(s, fmt)
except ValueError:
continue
raise ValueError(f"Data non valida: '{s}'. Usa AAAA-MM-GG HH:MM.")
def _due(r) -> datetime | None:
comps = r.dueDateComponents()
if comps is None:
return None
try:
return datetime(comps.year(), comps.month(), comps.day(), comps.hour() if comps.hour() != 9223372036854775807 else 0,
comps.minute() if comps.minute() != 9223372036854775807 else 0)
except Exception:
return None
def _fetch_incomplete():
store = _get_store()
pred = store.predicateForIncompleteRemindersWithDueDateStarting_ending_calendars_(None, None, None)
done, out = threading.Event(), []
def cb(items):
out.extend(list(items or []))
done.set()
store.fetchRemindersMatchingPredicate_completion_(pred, cb)
done.wait(30)
return out
def _fmt(r) -> str:
d = _due(r)
when = f" (entro {d:%d/%m %H:%M})" if d and (d.hour or d.minute) else (f" (entro {d:%d/%m})" if d else "")
return f"- {r.title()}{when} [{r.calendar().title()}]"
def elenca(params: dict, ctx: dict) -> str:
items = _fetch_incomplete()
quando = str(params.get("quando", "tutti")).lower()
today = datetime.now().date()
if quando == "oggi":
items = [r for r in items if (d := _due(r)) and d.date() <= today]
elif quando == "settimana":
items = [r for r in items if (d := _due(r)) and d.date() <= today + timedelta(days=7)]
items.sort(key=lambda r: (_due(r) or datetime.max))
if not items:
return "Nessun promemoria da fare" + (" per oggi." if quando == "oggi" else ".")
return f"Promemoria da fare ({len(items)}):\n" + "\n".join(_fmt(r) for r in items[:30])
def aggiungi(params: dict, ctx: dict) -> str:
import EventKit
from Foundation import NSDateComponents
store = _get_store()
titolo = str(params.get("titolo", "")).strip()
if not titolo:
return "Errore: serve il titolo."
r = EventKit.EKReminder.reminderWithEventStore_(store)
r.setTitle_(titolo)
lista = str(params.get("lista", "")).strip()
cal = None
if lista:
for c in store.calendarsForEntityType_(EventKit.EKEntityTypeReminder):
if c.title().lower() == lista.lower():
cal = c
r.setCalendar_(cal or store.defaultCalendarForNewReminders())
scadenza = _parse_dt(str(params.get("scadenza", "")))
if scadenza:
comps = NSDateComponents.alloc().init()
comps.setYear_(scadenza.year); comps.setMonth_(scadenza.month); comps.setDay_(scadenza.day)
comps.setHour_(scadenza.hour); comps.setMinute_(scadenza.minute)
r.setDueDateComponents_(comps)
from Foundation import NSDate
alarm = EventKit.EKAlarm.alarmWithAbsoluteDate_(NSDate.dateWithTimeIntervalSince1970_(scadenza.timestamp()))
r.addAlarm_(alarm)
if params.get("note"):
r.setNotes_(str(params["note"]))
ok, err = store.saveReminder_commit_error_(r, True, None)
return f"Promemoria aggiunto: {_fmt(r)[2:]}" if ok else f"Errore: {err}"
def completa(params: dict, ctx: dict) -> str:
titolo = str(params.get("titolo", "")).strip().lower()
if not titolo:
return "Errore: serve il titolo (anche parziale)."
found = [r for r in _fetch_incomplete() if titolo in (r.title() or "").lower()]
if not found:
return "Nessun promemoria con quel titolo."
if len(found) > 1 and not params.get("tutti"):
return "Ho trovato più promemoria, dimmi quale:\n" + "\n".join(_fmt(r) for r in found)
store = _get_store()
for r in found:
r.setCompleted_(True)
store.saveReminder_commit_error_(r, True, None)
return "Completato: " + ", ".join(r.title() for r in found)
TOOLS = [
{"name": "promemoria_elenca", "description": "Elenca i promemoria da fare dell'app Promemoria (tutti, di oggi o della settimana).",
"parameters": {"type": "object", "properties": {"quando": {"type": "string", "enum": ["tutti", "oggi", "settimana"]}}}, "run": elenca},
{"name": "promemoria_aggiungi", "description": "Aggiunge un promemoria (o una voce a una lista, per esempio 'Spesa'). Converti 'domani alle 17' in AAAA-MM-GG HH:MM usando la data del prompt.",
"parameters": {"type": "object", "properties": {"titolo": {"type": "string"}, "scadenza": {"type": "string", "description": "AAAA-MM-GG HH:MM, facoltativa."},
"lista": {"type": "string", "description": "Nome della lista (facoltativo)."}, "note": {"type": "string"}}, "required": ["titolo"]},
"run": aggiungi},
{"name": "promemoria_completa", "description": "Segna come fatto un promemoria cercandolo per titolo.",
"parameters": {"type": "object", "properties": {"titolo": {"type": "string"}, "tutti": {"type": "boolean", "description": "Completa tutti quelli che corrispondono."}}, "required": ["titolo"]},
"run": completa},
]