feat: get_categories tool, author resolution in get_activities, update CLAUDE.md (v0.4.8)
- get_categories(list_id): new tool filtering taskcategorysync by
sortingIndexByTaskList, returns {id, name, emoji} ordered by sort index
- get_activities: author IDs now resolved to display names (firstName) via
famlistfamily members; raw author_id preserved as separate field; member
lookup is non-fatal (falls back to raw ID on error)
- CLAUDE.md: tools table updated to reflect all implemented tools (v0.4.8)
- SPEC.md: full accgetallfamily response structure documented including
categories[] on tasks and category parameter investigation findings
- Category assignment in create/update task: all tested parameter names
ignored by API; still to verify (Service Worker blocks web inspection)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -331,6 +331,67 @@ def get_tasks(list_id: str, only_open: bool = True):
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: get_categories
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_categories(list_id: str) -> str:
|
||||
"""Return the task categories available for a list as JSON.
|
||||
|
||||
Categories are loaded from taskcategorysync (part of accgetallfamily) and
|
||||
filtered to those assigned to the given list via sortingIndexByTaskList.
|
||||
Shopping lists have predefined system categories (aisles); TODO lists
|
||||
typically only carry a single default system category.
|
||||
|
||||
Note: the parameter name for assigning a category when creating or
|
||||
updating a task is not yet verified via FW_DEBUG=1. See SPEC.md.
|
||||
|
||||
Args:
|
||||
list_id: List ID from get_lists (e.g. ``taskList/23431854_29740942``).
|
||||
|
||||
Returns:
|
||||
JSON list of {id, name, emoji} objects ordered by sortingIndex.
|
||||
"""
|
||||
try:
|
||||
data = _accgetallfamily()
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
try:
|
||||
raw_cats = data["a01"]["r"]["r"]["updatedCreated"]
|
||||
if not isinstance(raw_cats, list):
|
||||
raise TypeError("a01.r.r.updatedCreated is not a list")
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected taskcategorysync response structure", "raw": data.get("a01")},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
# Collect categories assigned to this list, ordered by their sorting index.
|
||||
matched: list[tuple[int, dict[str, Any]]] = []
|
||||
seen: set[str] = set()
|
||||
for cat in raw_cats:
|
||||
sorting = cat.get("sortingIndexByTaskList") or {}
|
||||
if list_id not in sorting:
|
||||
continue
|
||||
cat_id: str = cat.get("metaId", "")
|
||||
if cat_id in seen:
|
||||
continue
|
||||
seen.add(cat_id)
|
||||
sort_val = int(sorting.get(list_id, cat.get("initialSortingIndex", 0)) or 0)
|
||||
matched.append((sort_val, cat))
|
||||
|
||||
matched.sort(key=lambda t: t[0])
|
||||
result = [
|
||||
{"id": cat.get("metaId"), "name": cat.get("name"), "emoji": cat.get("emoji")}
|
||||
for _, cat in matched
|
||||
]
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: get_activities
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -344,6 +405,19 @@ def get_activities(limit: int = 20):
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
# Load member data to resolve author IDs to display names.
|
||||
# Non-fatal: fall back to raw account IDs when member lookup fails.
|
||||
author_map: dict[str, str] = {}
|
||||
try:
|
||||
for circle in _famlistfamily():
|
||||
for member in circle.get("members") or []:
|
||||
acc_id: str = member.get("accountId", "")
|
||||
display = member.get("firstName") or member.get("name") or acc_id
|
||||
if acc_id:
|
||||
author_map[acc_id] = display
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
with FamilyWallClient() as client:
|
||||
client.login(email, password)
|
||||
@@ -375,13 +449,15 @@ def get_activities(limit: int = 20):
|
||||
|
||||
result = []
|
||||
for item in raw_activities:
|
||||
raw_author: str = item.get("accountId", "")
|
||||
result.append(
|
||||
{
|
||||
"id": item.get("metaId"),
|
||||
"type": item.get("refType"),
|
||||
"text": item.get("text"),
|
||||
"date": item.get("creationDate"),
|
||||
"author": item.get("accountId"),
|
||||
"author": author_map.get(raw_author, raw_author),
|
||||
"author_id": raw_author,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user