diff --git a/TOOLS.md b/TOOLS.md index fb121df..185a02a 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -128,14 +128,16 @@ $SKILL_DIR/scripts/calendar.sh reply --envelope-id 42 --action decline \ # 待办管理 $SKILL_DIR/scripts/calendar.sh todo add --summary "跟进报销" --due "2026-03-25" --priority high $SKILL_DIR/scripts/calendar.sh todo list +$SKILL_DIR/scripts/calendar.sh todo edit --match "报销" --due "2026-03-28" $SKILL_DIR/scripts/calendar.sh todo complete --match "报销" +$SKILL_DIR/scripts/calendar.sh todo delete --match "报销" $SKILL_DIR/scripts/calendar.sh todo check # 每日摘要(cron) # 查看日历(检查冲突) khal list today 7d ``` -**支持操作**: 发送邀请 (`send`)、接受/拒绝/暂定 (`reply`)、待办管理 (`todo add/list/complete/delete/check`) +**支持操作**: 发送邀请 (`send`)、接受/拒绝/暂定 (`reply`)、待办管理 (`todo add/list/edit/complete/delete/check`) **依赖**: himalaya(邮件)、vdirsyncer(CalDAV 同步)、khal(查看日历)、todoman(待办管理) **同步**: 发送/回复/待办变更后自动 `vdirsyncer sync`,心跳也会定期同步 **注意**: 发送日历邀请属于对外邮件,需先确认 diff --git a/skills/calendar/SKILL.md b/skills/calendar/SKILL.md index 0765caf..c611c9d 100644 --- a/skills/calendar/SKILL.md +++ b/skills/calendar/SKILL.md @@ -42,6 +42,7 @@ $SKILL_DIR/scripts/calendar.sh reply [options] # Manage todos $SKILL_DIR/scripts/calendar.sh todo add [options] $SKILL_DIR/scripts/calendar.sh todo list [options] +$SKILL_DIR/scripts/calendar.sh todo edit [options] $SKILL_DIR/scripts/calendar.sh todo complete [options] $SKILL_DIR/scripts/calendar.sh todo delete [options] $SKILL_DIR/scripts/calendar.sh todo check @@ -198,6 +199,23 @@ $SKILL_DIR/scripts/calendar.sh todo list # pending only $SKILL_DIR/scripts/calendar.sh todo list --all # include completed ``` +### `todo edit` — Modify a Todo + +```bash +$SKILL_DIR/scripts/calendar.sh todo edit --match "保险报销" --due "2026-03-26" +$SKILL_DIR/scripts/calendar.sh todo edit --uid "abc123@openclaw" --priority high +$SKILL_DIR/scripts/calendar.sh todo edit --match "census" --due "2026-03-28" --priority low +``` + +| Flag | Required | Description | +|-----------------|----------|-----------------------------------------------------| +| `--uid` | * | Todo UID | +| `--match` | * | Match on summary text | +| `--due` | No | New due date (YYYY-MM-DD) | +| `--priority` | No | New priority: `high`, `medium`, or `low` | + +\* One of `--uid` or `--match` is required. At least one edit flag must be provided. + ### `todo complete` — Mark as Done ```bash diff --git a/skills/calendar/TESTING.md b/skills/calendar/TESTING.md index 683947b..eb4da48 100644 --- a/skills/calendar/TESTING.md +++ b/skills/calendar/TESTING.md @@ -188,7 +188,55 @@ $SKILL_DIR/scripts/calendar.sh todo list --all - [ ] Priority grouping is correct in wrapper output - [ ] `--all` flag works (same output when none are completed) -## 10. Complete a Todo +## 10. Edit a Todo + +Change the due date and priority of the test todo from step 8. + +```bash +# Edit due date +$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" --due "$(date -d '+5 days' +%Y-%m-%d)" + +# Verify change +$SKILL_DIR/scripts/calendar.sh todo list +``` + +**Verify:** +- [ ] Script exits without error +- [ ] Output shows "Updated todo: Test Todo" with the change +- [ ] `todo list` shows the new due date +- [ ] `vdirsyncer sync` ran + +```bash +# Edit priority +$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" --priority high + +# Verify change +$SKILL_DIR/scripts/calendar.sh todo list +``` + +**Verify:** +- [ ] Priority changed to high +- [ ] Todo appears under the high priority group in formatted output + +```bash +# Edit multiple fields at once +$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" --due "$TEST_DATE" --priority low +``` + +**Verify:** +- [ ] Both due date and priority updated in one command + +```bash +# Error: no matching todo +$SKILL_DIR/scripts/calendar.sh todo edit --match "nonexistent" --due "$TEST_DATE" +# Should print error and exit non-zero + +# Error: no fields to edit +$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" +# Should print "Nothing to change" message +``` + +## 11. Complete a Todo ```bash $SKILL_DIR/scripts/calendar.sh todo complete --match "Test Todo" @@ -200,7 +248,7 @@ $SKILL_DIR/scripts/calendar.sh todo complete --match "Test Todo" - [ ] `$SKILL_DIR/scripts/calendar.sh todo list --all` — appears as completed (with checkmark) - [ ] `vdirsyncer sync` ran -## 11. Delete a Todo +## 12. Delete a Todo Create a second test todo, then delete it. @@ -223,7 +271,7 @@ $SKILL_DIR/scripts/calendar.sh todo delete --match "Delete Me" - [ ] `todo list` (todoman) does not show "Delete Me Todo" - [ ] `vdirsyncer sync` ran -## 12. Todo Check (Cron Output) +## 13. Todo Check (Cron Output) ```bash # Create a test todo @@ -245,7 +293,7 @@ $SKILL_DIR/scripts/calendar.sh todo check # Should produce no output ``` -## 13. Regression: Existing Invite Commands +## 14. Regression: Existing Invite Commands Verify the rename didn't break VEVENT flow. diff --git a/skills/calendar/scripts/cal_tool.py b/skills/calendar/scripts/cal_tool.py index 9326960..00d6bd5 100644 --- a/skills/calendar/scripts/cal_tool.py +++ b/skills/calendar/scripts/cal_tool.py @@ -10,6 +10,7 @@ Subcommands: python calendar.py reply [options] # accept/decline/tentative python calendar.py todo add [options] # create a VTODO task python calendar.py todo list [options] # list pending tasks + python calendar.py todo edit [options] # edit a task's fields python calendar.py todo complete [options] # mark task as done python calendar.py todo delete [options] # remove a task python calendar.py todo check # daily digest for cron @@ -600,6 +601,58 @@ def cmd_todo_complete(args): _sync_calendar() +def _find_todo(uid="", match=""): + """Find a single todo by UID or summary match. Returns (todoman_id, todo_dict).""" + todos = _todoman_list_json("--sort", "due") + if uid: + matches = [t for t in todos if uid in (t.get("uid") or "")] + if not matches: + print(f"Error: No todo with UID '{uid}'", file=sys.stderr) + sys.exit(1) + elif match: + matches = [t for t in todos if match in (t.get("summary") or "")] + if not matches: + print(f"Error: No todo matching '{match}'", file=sys.stderr) + sys.exit(1) + if len(matches) > 1: + print(f"Error: Multiple todos match '{match}':", file=sys.stderr) + for t in matches: + print(f" - {t.get('summary')} (uid: {t.get('uid')})", file=sys.stderr) + sys.exit(1) + else: + print("Error: --uid or --match is required", file=sys.stderr) + sys.exit(1) + return matches[0]["id"], matches[0] + + +def cmd_todo_edit(args): + """Edit a todo's fields via todoman.""" + todo_id, matched = _find_todo(uid=args.uid, match=args.match) + + # Build todoman edit command args + todo_args = [] + changes = [] + + if args.due: + todo_args += ["--due", args.due] + changes.append(f"due -> {args.due}") + if args.priority: + todo_args += ["--priority", args.priority] + prio_label = PRIORITY_LABELS.get(PRIORITY_MAP.get(args.priority, 5), "中") + changes.append(f"priority -> {prio_label}") + + if not changes: + print("Nothing to change. Specify at least one of --due, --priority.") + return + + # Run todoman edit (non-interactive) + _run_todoman("edit", str(todo_id), "--no-interactive", *todo_args) + print(f"Updated todo: {matched.get('summary')}") + for c in changes: + print(f" {c}") + _sync_calendar() + + def cmd_todo_delete(args): """Delete a todo via todoman.""" if args.uid: @@ -688,6 +741,13 @@ def main(): comp_p.add_argument("--uid", default="", help="Todo UID") comp_p.add_argument("--match", default="", help="Fuzzy match on summary") + # todo edit + edit_p = todo_sub.add_parser("edit", help="Edit a todo's fields") + edit_p.add_argument("--uid", default="", help="Todo UID") + edit_p.add_argument("--match", default="", help="Fuzzy match on summary") + edit_p.add_argument("--due", default="", help="New due date (YYYY-MM-DD)") + edit_p.add_argument("--priority", default="", choices=["", "high", "medium", "low"], help="New priority") + # todo delete del_p = todo_sub.add_parser("delete", help="Delete a todo") del_p.add_argument("--uid", default="", help="Todo UID") @@ -709,6 +769,8 @@ def main(): cmd_todo_list(args) elif args.todo_command == "complete": cmd_todo_complete(args) + elif args.todo_command == "edit": + cmd_todo_edit(args) elif args.todo_command == "delete": cmd_todo_delete(args) elif args.todo_command == "check": diff --git a/skills/calendar/scripts/calendar.sh b/skills/calendar/scripts/calendar.sh index cb178de..0f50e5f 100755 --- a/skills/calendar/scripts/calendar.sh +++ b/skills/calendar/scripts/calendar.sh @@ -6,6 +6,7 @@ # ./calendar.sh reply [options] # accept/decline/tentative # ./calendar.sh todo add [options] # create a todo # ./calendar.sh todo list [options] # list pending todos +# ./calendar.sh todo edit [options] # edit a todo's fields # ./calendar.sh todo complete [options] # mark todo as done # ./calendar.sh todo delete [options] # remove a todo # ./calendar.sh todo check # daily digest for cron