feat(heartbeat): save emails to KB + fix memory symlink access
- heartbeat saves unread whitelisted emails via email_process --save --json - fix: add --add-dir so Claude CLI subprocess can access memory/ symlink - email_check/process: use BODY.PEEK[] to avoid marking emails as read - email_process: simplify credential loading via credential_store only - config: heartbeat interval 30→120min, quiet hours end 08→07 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,7 +77,7 @@ def check_inbox(unread_only=True, limit=10):
|
||||
|
||||
emails = []
|
||||
for eid in reversed(email_ids): # Newest first
|
||||
status, msg_data = mail.fetch(eid, "(RFC822)")
|
||||
status, msg_data = mail.fetch(eid, "(BODY.PEEK[])")
|
||||
if status != "OK":
|
||||
continue
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ Usage:
|
||||
|
||||
import imaplib
|
||||
import email
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
@@ -19,34 +18,13 @@ from email.header import decode_header
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Try keyring first, fall back to .env
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
try:
|
||||
from src.credential_store import get_secret
|
||||
except ImportError:
|
||||
get_secret = lambda name: None
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from src.credential_store import get_secret
|
||||
|
||||
def _get(keyring_name, env_name, default=''):
|
||||
val = get_secret(keyring_name)
|
||||
if val:
|
||||
return val
|
||||
return os.environ.get(env_name, default)
|
||||
|
||||
# Load .env as fallback
|
||||
env_path = Path(__file__).parent.parent / '.env'
|
||||
if env_path.exists():
|
||||
with open(env_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#') and '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
os.environ.setdefault(key, value)
|
||||
|
||||
# Config
|
||||
IMAP_SERVER = _get('email_server', 'EMAIL_SERVER', 'mail.romfast.ro')
|
||||
IMAP_SERVER = get_secret("email_server") or "mail.romfast.ro"
|
||||
IMAP_PORT = 993
|
||||
IMAP_USER = _get('email_user', 'EMAIL_USER', 'echo@romfast.ro')
|
||||
IMAP_PASS = _get('email_password', 'EMAIL_PASSWORD')
|
||||
IMAP_USER = get_secret("email_user") or "echo@romfast.ro"
|
||||
IMAP_PASS = get_secret("email_password") or ""
|
||||
|
||||
# Whitelist - only process emails from these addresses
|
||||
WHITELIST = [
|
||||
@@ -54,7 +32,8 @@ WHITELIST = [
|
||||
'marius.mutu@romfast.ro',
|
||||
]
|
||||
|
||||
KB_PATH = Path(__file__).parent.parent / 'memory' / 'kb' / 'emails'
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
KB_PATH = PROJECT_ROOT / "memory" / "kb" / "emails"
|
||||
|
||||
def slugify(text: str, max_len: int = 50) -> str:
|
||||
"""Convert text to URL-friendly slug"""
|
||||
@@ -121,7 +100,10 @@ def list_emails(show_all=False):
|
||||
emails = []
|
||||
|
||||
for eid in email_ids:
|
||||
status, data = mail.fetch(eid, '(RFC822)')
|
||||
# BODY.PEEK does not mark as read
|
||||
status, data = mail.fetch(eid, "(BODY.PEEK[])")
|
||||
if status != "OK":
|
||||
continue
|
||||
msg = email.message_from_bytes(data[0][1])
|
||||
|
||||
from_addr = decode_mime_header(msg['From'])
|
||||
@@ -227,25 +209,29 @@ def save_unread_emails():
|
||||
return results
|
||||
|
||||
if __name__ == "__main__":
|
||||
if '--save' in sys.argv:
|
||||
as_json = "--json" in sys.argv
|
||||
|
||||
if "--save" in sys.argv:
|
||||
results = save_unread_emails()
|
||||
for r in results:
|
||||
if r['ok']:
|
||||
print(f"✅ Salvat: {r['file']}")
|
||||
else:
|
||||
print(f"❌ Eroare: {r['error']}")
|
||||
if not results:
|
||||
print("Niciun email nou de la adrese whitelisted.")
|
||||
if as_json:
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
if not results:
|
||||
print("Niciun email nou de la adrese whitelisted.")
|
||||
for r in results:
|
||||
if r["ok"]:
|
||||
print(f"✅ Salvat: {r['file']}")
|
||||
else:
|
||||
print(f"❌ Eroare: {r['error']}")
|
||||
else:
|
||||
show_all = '--all' in sys.argv
|
||||
show_all = "--all" in sys.argv
|
||||
emails = list_emails(show_all=show_all)
|
||||
|
||||
if not emails:
|
||||
print("Inbox gol." if show_all else "Niciun email necitit.")
|
||||
else:
|
||||
for em in emails:
|
||||
wl = "✅" if em['whitelisted'] else "⚠️"
|
||||
wl = "✅" if em["whitelisted"] else "⚠️"
|
||||
print(f"{wl} [{em['id']}] {em['subject']}")
|
||||
print(f" De la: {em['from']}")
|
||||
print(f" Data: {em['date']}")
|
||||
print(f" Data: {em['date']}")
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user