Odysseus v1.0
This commit is contained in:
143
scripts/odysseus-contacts
Executable file
143
scripts/odysseus-contacts
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""odysseus-contacts — Unix-style CLI for the contacts feature.
|
||||
|
||||
Talks to the same CardDAV server the web UI does, using credentials
|
||||
from `data/settings.json` (set via the Settings → Connections panel).
|
||||
Output is JSON on stdout, errors on stderr, non-zero exit on failure.
|
||||
|
||||
odysseus-contacts list | jq -r '.[] | .name + "\\t" + .email'
|
||||
odysseus-contacts search alice
|
||||
odysseus-contacts add --name "Alice Doe" --email alice@example.com
|
||||
|
||||
Subcommands:
|
||||
list List all contacts
|
||||
search Filter contacts by case-insensitive substring match
|
||||
add Add a new contact (name + email)
|
||||
config Show the current CardDAV config (URL/user; password masked)
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
import os, sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "_lib"))
|
||||
from cli import quiet_logs, emit, fail, common_parser, run, REPO_ROOT as _REPO_ROOT
|
||||
quiet_logs()
|
||||
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def quiet_logs() -> None:
|
||||
level_name = os.environ.get("LOG_LEVEL", "WARNING").upper()
|
||||
level = getattr(logging, level_name, logging.WARNING)
|
||||
root = logging.getLogger()
|
||||
root.setLevel(level)
|
||||
for handler in root.handlers:
|
||||
handler.setLevel(level)
|
||||
|
||||
|
||||
quiet_logs()
|
||||
|
||||
try:
|
||||
from routes.contacts_routes import (
|
||||
_get_carddav_config,
|
||||
_fetch_contacts,
|
||||
_create_contact,
|
||||
)
|
||||
quiet_logs()
|
||||
except ModuleNotFoundError as e:
|
||||
sys.stderr.write(f"error: {e}\nhint: run from repo root with venv active.\n")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def fail(msg: str, code: int = 1) -> None:
|
||||
sys.stderr.write(f"error: {msg}\n")
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
# ─── list ────────────────────────────────────────────────────────────
|
||||
|
||||
def cmd_list(args) -> None:
|
||||
cfg = _get_carddav_config()
|
||||
if not cfg["url"]:
|
||||
fail("CardDAV not configured. Set carddav_url/username/password in the web UI.")
|
||||
contacts = _fetch_contacts(force=args.refresh)
|
||||
emit(contacts, args)
|
||||
|
||||
|
||||
# ─── search ──────────────────────────────────────────────────────────
|
||||
|
||||
def cmd_search(args) -> None:
|
||||
cfg = _get_carddav_config()
|
||||
if not cfg["url"]:
|
||||
fail("CardDAV not configured.")
|
||||
q = args.query.lower()
|
||||
contacts = _fetch_contacts()
|
||||
matches = [
|
||||
c for c in contacts
|
||||
if q in (c.get("name") or "").lower() or q in (c.get("email") or "").lower()
|
||||
]
|
||||
emit(matches, args)
|
||||
|
||||
|
||||
# ─── add ─────────────────────────────────────────────────────────────
|
||||
|
||||
def cmd_add(args) -> None:
|
||||
cfg = _get_carddav_config()
|
||||
if not cfg["url"]:
|
||||
fail("CardDAV not configured.")
|
||||
ok = _create_contact(args.name, args.email)
|
||||
if not ok:
|
||||
fail("CardDAV PUT failed (see LOG_LEVEL=DEBUG for detail)")
|
||||
emit({"ok": True, "name": args.name, "email": args.email}, args)
|
||||
|
||||
|
||||
# ─── config ──────────────────────────────────────────────────────────
|
||||
|
||||
def cmd_config(args) -> None:
|
||||
cfg = _get_carddav_config()
|
||||
safe = dict(cfg)
|
||||
if safe.get("password"):
|
||||
safe["password"] = "***"
|
||||
emit(safe, args)
|
||||
|
||||
|
||||
# ─── argparse ────────────────────────────────────────────────────────
|
||||
|
||||
def _build_parser() -> argparse.ArgumentParser:
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument("--pretty", action="store_true", help="Pretty-print JSON")
|
||||
|
||||
p = argparse.ArgumentParser(
|
||||
prog="odysseus-contacts",
|
||||
description="Shell-friendly wrapper around the Odysseus contacts (CardDAV) feature.",
|
||||
parents=[common],
|
||||
)
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
pl = sub.add_parser("list", help="list all contacts", parents=[common])
|
||||
pl.add_argument("--refresh", action="store_true", help="bypass cache and re-fetch")
|
||||
pl.set_defaults(func=cmd_list)
|
||||
|
||||
ps = sub.add_parser("search", help="filter by name/email substring", parents=[common])
|
||||
ps.add_argument("query")
|
||||
ps.set_defaults(func=cmd_search)
|
||||
|
||||
pa = sub.add_parser("add", help="add a new contact", parents=[common])
|
||||
pa.add_argument("--name", required=True)
|
||||
pa.add_argument("--email", required=True)
|
||||
pa.set_defaults(func=cmd_add)
|
||||
|
||||
pc = sub.add_parser("config", help="show CardDAV config (password masked)", parents=[common])
|
||||
pc.set_defaults(func=cmd_config)
|
||||
|
||||
return p
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(_build_parser()))
|
||||
Reference in New Issue
Block a user