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

101 lines
5.1 KiB
Python

"""File e documenti: cerca con Spotlight, elenca cartelle, legge testo, PDF e Word."""
from __future__ import annotations
import subprocess
from datetime import datetime
from pathlib import Path
HOME = Path.home()
MAX_CHARS = 6000
FOLDERS = {"desktop": HOME / "Desktop", "scrivania": HOME / "Desktop", "documenti": HOME / "Documents", "download": HOME / "Downloads",
"immagini": HOME / "Pictures", "musica": HOME / "Music", "home": HOME}
def _folder(name: str) -> Path:
n = (name or "").strip().lower()
if n in FOLDERS:
return FOLDERS[n]
p = Path(name).expanduser()
return p if p.exists() else HOME
def cerca(params: dict, ctx: dict) -> str:
q = str(params.get("query", "")).strip()
if not q:
return "Errore: serve il campo 'query'."
folder = _folder(str(params.get("cartella", "home")))
kind = str(params.get("tipo", "")).lower()
query = f'kMDItemFSName == "*{q}*"cd || kMDItemTextContent == "*{q}*"cd'
if kind == "pdf":
query = f'({query}) && kMDItemContentType == "com.adobe.pdf"'
elif kind in ("immagine", "immagini"):
query = f'({query}) && kMDItemContentTypeTree == "public.image"'
elif kind in ("documento", "documenti"):
query = f'({query}) && (kMDItemContentTypeTree == "public.text" || kMDItemContentType == "com.adobe.pdf" || kMDItemContentType == "org.openxmlformats.wordprocessingml.document")'
res = subprocess.run(["mdfind", "-onlyin", str(folder), query], capture_output=True, text=True, timeout=30)
paths = [Path(p) for p in res.stdout.splitlines() if p and "/Library/" not in p][:200]
if not paths:
return f"Nessun file trovato per '{q}' in {folder.name or 'home'}."
paths.sort(key=lambda p: p.stat().st_mtime if p.exists() else 0, reverse=True)
lines = [f"- {p.name} ({datetime.fromtimestamp(p.stat().st_mtime):%d/%m/%Y}) — {p.parent}" for p in paths[:12] if p.exists()]
return f"Trovati {len(paths)} file per '{q}':\n" + "\n".join(lines)
def elenca(params: dict, ctx: dict) -> str:
folder = _folder(str(params.get("cartella", "desktop")))
items = sorted([p for p in folder.iterdir() if not p.name.startswith(".")], key=lambda p: p.stat().st_mtime, reverse=True)
if not items:
return f"La cartella {folder.name} è vuota."
lines = [f"- {p.name}{'/' if p.is_dir() else ''} ({datetime.fromtimestamp(p.stat().st_mtime):%d/%m %H:%M})" for p in items[:25]]
return f"{folder.name} ({len(items)} elementi, i più recenti):\n" + "\n".join(lines)
def _read(path: Path) -> str:
ext = path.suffix.lower()
if ext == ".pdf":
from pypdf import PdfReader
reader = PdfReader(str(path))
return "\n".join((page.extract_text() or "") for page in reader.pages[:30])
if ext == ".docx":
import docx
d = docx.Document(str(path))
return "\n".join(p.text for p in d.paragraphs)
if ext in (".txt", ".md", ".csv", ".json", ".py", ".js", ".ts", ".html", ".rtf", ".log"):
return path.read_text(encoding="utf-8", errors="replace")
try:
return subprocess.run(["textutil", "-convert", "txt", "-stdout", str(path)], capture_output=True, text=True, timeout=30).stdout
except Exception:
return ""
def leggi(params: dict, ctx: dict) -> str:
name = str(params.get("percorso", "")).strip()
if not name:
return "Errore: serve il percorso o il nome del file."
path = Path(name).expanduser()
if not path.exists():
res = subprocess.run(["mdfind", "-onlyin", str(HOME), f'kMDItemFSName == "*{path.name}*"cd'], capture_output=True, text=True, timeout=20)
cands = [Path(p) for p in res.stdout.splitlines() if p and "/Library/" not in p]
if not cands:
return f"File non trovato: {name}"
cands.sort(key=lambda p: p.stat().st_mtime, reverse=True)
path = cands[0]
try:
text = _read(path)
except Exception as err:
return f"Non riesco a leggere {path.name}: {err}"
text = " ".join(text.split())
if not text:
return f"{path.name}: nessun testo estraibile."
return f"{path.name} ({len(text)} caratteri):\n{text[:MAX_CHARS]}{' […]' if len(text) > MAX_CHARS else ''}"
TOOLS = [
{"name": "file_cerca", "description": "Cerca file per nome o contenuto con Spotlight, in tutta la home o in una cartella (desktop, documenti, download…). Tipo: pdf, immagine, documento.",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}, "cartella": {"type": "string"}, "tipo": {"type": "string"}}, "required": ["query"]}, "run": cerca},
{"name": "file_elenca", "description": "Elenca i file di una cartella (desktop, documenti, download o un percorso), i più recenti prima.",
"parameters": {"type": "object", "properties": {"cartella": {"type": "string"}}}, "run": elenca},
{"name": "file_leggi", "description": "Legge il testo di un file (txt, md, PDF, Word, pagine, rtf) per riassumerlo o rispondere a domande. Accetta un percorso o solo il nome (lo cerca).",
"parameters": {"type": "object", "properties": {"percorso": {"type": "string"}}, "required": ["percorso"]}, "run": leggi},
]