From 0910bd5d5c220995b06fa1b1e5b857ff2e515316 Mon Sep 17 00:00:00 2001 From: Yanxin Lu Date: Tue, 31 Mar 2026 13:33:04 -0700 Subject: [PATCH] contacts: support multiple address book collections Migadu exposes "family" and "business" address books via CardDAV. The contacts script now searches all subdirs under contacts/ and adds new contacts to the "family" collection by default. --- TOOLS.md | 2 +- skills/calendar/TESTING.md | 2 +- skills/contacts/SKILL.md | 4 ++-- skills/contacts/scripts/contacts.py | 27 ++++++++++++++++----------- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/TOOLS.md b/TOOLS.md index fd9df7f..94c13cb 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -51,7 +51,7 @@ cat msg.txt | $HIMALAYA template send # 发送邮件(校验收件人) ### 通讯录(contacts) **目录**: `~/.openclaw/workspace/skills/contacts/` -**数据**: `~/.openclaw/workspace/contacts/default/`(vCard .vcf 文件,CardDAV 同步) +**数据**: `~/.openclaw/workspace/contacts/`(vCard .vcf 文件,按 Migadu 地址簿分目录,CardDAV 同步) ```bash CONTACTS=~/.openclaw/workspace/skills/contacts/scripts/contacts.sh diff --git a/skills/calendar/TESTING.md b/skills/calendar/TESTING.md index de09cfe..c91f9dc 100644 --- a/skills/calendar/TESTING.md +++ b/skills/calendar/TESTING.md @@ -39,7 +39,7 @@ $CONTACTS_DIR/scripts/contacts.sh list - [ ] `contact add` prints "Added contact: ..." - [ ] Second `contact add` prints "Updated contact: ... — added ..." - [ ] `contact list` shows all contacts with email types -- [ ] `.vcf` files created in `~/.openclaw/workspace/contacts/default/` +- [ ] `.vcf` files created in `~/.openclaw/workspace/contacts/family/` ## 2. Recipient Resolution (Send Validation) diff --git a/skills/contacts/SKILL.md b/skills/contacts/SKILL.md index 8ca2588..c1c0baa 100644 --- a/skills/contacts/SKILL.md +++ b/skills/contacts/SKILL.md @@ -18,7 +18,7 @@ LLMs can hallucinate email addresses — inventing plausible-looking addresses f - Python 3 (no external dependencies) - `vdirsyncer` configured with a CardDAV pair for contacts sync -- Contacts directory: `~/.openclaw/workspace/contacts/default/` +- Contacts directory: `~/.openclaw/workspace/contacts/` (subdirs per address book, e.g. `family/`, `business/`) ## Usage @@ -64,4 +64,4 @@ Unknown addresses are **rejected** with the available contacts list shown. ## Data -Contacts are stored as vCard 3.0 `.vcf` files in `~/.openclaw/workspace/contacts/default/`, synced to Migadu CardDAV via vdirsyncer. Filenames are `{uid}.vcf`. +Contacts are stored as vCard 3.0 `.vcf` files in `~/.openclaw/workspace/contacts//`, synced to Migadu CardDAV via vdirsyncer. Collections match Migadu address books (e.g. `family/`, `business/`). New contacts are added to `family/` by default. The resolve command searches all collections. diff --git a/skills/contacts/scripts/contacts.py b/skills/contacts/scripts/contacts.py index 5f012a3..86fb06f 100755 --- a/skills/contacts/scripts/contacts.py +++ b/skills/contacts/scripts/contacts.py @@ -22,7 +22,8 @@ from pathlib import Path # Config # --------------------------------------------------------------------------- -CONTACTS_DIR = Path.home() / ".openclaw" / "workspace" / "contacts" / "default" +CONTACTS_ROOT = Path.home() / ".openclaw" / "workspace" / "contacts" +DEFAULT_COLLECTION = "family" # default address book for new contacts PRODID = "-//OpenClaw//Contacts//EN" # --------------------------------------------------------------------------- @@ -57,17 +58,20 @@ def _parse_vcf(path): def _load_contacts(): - """Load all contacts from CONTACTS_DIR. Returns list of parsed contact dicts.""" - if not CONTACTS_DIR.is_dir(): + """Load all contacts from all collections under CONTACTS_ROOT.""" + if not CONTACTS_ROOT.is_dir(): return [] contacts = [] - for vcf_path in sorted(CONTACTS_DIR.glob("*.vcf")): - try: - contact = _parse_vcf(vcf_path) - if contact["fn"] or contact["emails"]: - contacts.append(contact) - except Exception: + for subdir in sorted(CONTACTS_ROOT.iterdir()): + if not subdir.is_dir(): continue + for vcf_path in sorted(subdir.glob("*.vcf")): + try: + contact = _parse_vcf(vcf_path) + if contact["fn"] or contact["emails"]: + contacts.append(contact) + except Exception: + continue return contacts @@ -225,7 +229,8 @@ def cmd_list(args): def cmd_add(args): """Add a new contact (creates or updates a .vcf file).""" - CONTACTS_DIR.mkdir(parents=True, exist_ok=True) + target_dir = CONTACTS_ROOT / DEFAULT_COLLECTION + target_dir.mkdir(parents=True, exist_ok=True) emails = [{"address": args.email, "type": args.type or ""}] nickname = args.nickname or "" @@ -251,7 +256,7 @@ def cmd_add(args): # New contact uid = f"{uuid.uuid4()}@openclaw" vcf_str = _build_vcf(args.name, emails, nickname, uid) - dest = CONTACTS_DIR / f"{uid}.vcf" + dest = target_dir / f"{uid}.vcf" dest.write_text(vcf_str, encoding="utf-8") print(f"Added contact: {args.name} <{args.email}>") _sync_contacts()