coord: add msg purge --before command for cleaning dealt-with messages
Adds c_msg_purge to coord.py + SKILL.md doc. Deletes coordination messages older than a
date cutoff via DELETE /api/coord/messages/{id}. Safety: --before is required (can't wipe
the store by accident), DRY-RUN by default (previews; --yes to actually delete), optional
--to scopes to one recipient session, paginates over the API's 1000-row limit cap, logs
partial failures. Replaces the ad-hoc curl loop used to purge 208 stale messages this session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "$CLAUDETOOLS_ROOT/.claude/skills
|
||||
| `msg send <to> "<subject>" --body-file <path>` | Same, body from a file (use for long/multi-line bodies — avoids shell-quoting breakage). |
|
||||
| `msg inbox [--all]` | Your unread messages (or `--all`). Print these prominently per the coord rule. |
|
||||
| `msg read <id>` | Mark a message read. |
|
||||
| `msg purge --before YYYY-MM-DD [--to <session>] [--yes]` | Delete dealt-with messages older than the cutoff date. **DRY-RUN by default** (previews what would go); add `--yes` to actually delete. `--to` scopes to one recipient session. Refuses to run without `--before` (can't wipe the store by accident). Destructive + fleet-wide — coordination msgs are ephemeral; the durable record is in session logs. |
|
||||
| `todo add "<text>" [--user U] [--project KEY] [--parent ID] [--source TXT] [--auto]` | Create a todo (auto-fills `created_by_user`/`created_by_machine`). `--text-file` for long text. |
|
||||
| `todo list [--user U] [--project KEY] [--status pending\|done\|all]` | List todos. |
|
||||
| `todo done <id>` | Mark a todo done (sets `completed_by`). |
|
||||
|
||||
@@ -14,6 +14,9 @@ Usage:
|
||||
<to>: ALL | <machine> | <machine>/claude-main
|
||||
coord.py msg inbox [--all] # messages to me (unread by default)
|
||||
coord.py msg read <id>
|
||||
coord.py msg purge --before YYYY-MM-DD [--to <session|ALL|machine>] [--yes]
|
||||
# delete dealt-with messages older than the cutoff. DRY-RUN by default;
|
||||
# add --yes to actually delete. --to scopes to one recipient session.
|
||||
coord.py todo add <text> [--text-file PATH] [--user U] [--project KEY] [--parent ID] [--source TEXT]
|
||||
coord.py todo list [--user U] [--project KEY] [--status pending|done|all]
|
||||
coord.py todo done <id>
|
||||
@@ -150,6 +153,56 @@ def c_msg_read(a):
|
||||
print(f"[coord] marked read: {a.id}")
|
||||
|
||||
|
||||
def c_msg_purge(a):
|
||||
"""Delete dealt-with messages older than --before (date cutoff). DRY-RUN unless --yes.
|
||||
|
||||
Refuses to run without --before so it can never wipe the whole store by accident.
|
||||
Coordination messages are ephemeral; the durable record is in session logs.
|
||||
"""
|
||||
before = (a.before or "").strip()
|
||||
if not (len(before) == 10 and before[4] == "-" and before[7] == "-"
|
||||
and before.replace("-", "").isdigit()):
|
||||
print("[coord] --before must be YYYY-MM-DD", file=sys.stderr); sys.exit(2)
|
||||
to = resolve_to(a.to) if a.to else None
|
||||
# Fetch all messages, paginating over the API's 1000-row limit cap.
|
||||
msgs, skip = [], 0
|
||||
while True:
|
||||
q = {"limit": 1000, "skip": skip}
|
||||
if to:
|
||||
q["to_session"] = to
|
||||
st, r = call("GET", "/messages", query=q); die(st, r, ok=(200,))
|
||||
batch = listify(r, "messages")
|
||||
msgs += batch
|
||||
if len(batch) < 1000:
|
||||
break
|
||||
skip += 1000
|
||||
# Strictly before the cutoff DATE (compare the date prefix, so the cutoff day is kept).
|
||||
targets = [m for m in msgs if (m.get("created_at") or "")[:10] < before]
|
||||
scope = f" to {to}" if to else ""
|
||||
print(f"[coord] {len(targets)} message(s){scope} before {before} (of {len(msgs)} fetched)")
|
||||
if not targets:
|
||||
return
|
||||
if not a.yes:
|
||||
for m in targets[:20]:
|
||||
print(f" {(m.get('created_at') or '')[:16]} | {m.get('from_session')} -> "
|
||||
f"{m.get('to_session')} | {str(m.get('subject',''))[:50]}")
|
||||
if len(targets) > 20:
|
||||
print(f" ... and {len(targets) - 20} more")
|
||||
print(f"[coord] DRY RUN - re-run with --yes to delete these {len(targets)} message(s)")
|
||||
return
|
||||
ok = fail = 0
|
||||
for m in targets:
|
||||
st, r = call("DELETE", f"/messages/{m.get('id')}")
|
||||
if st in (200, 204):
|
||||
ok += 1
|
||||
else:
|
||||
fail += 1
|
||||
print(f"[coord] purged {ok} message(s)" + (f", {fail} FAILED" if fail else ""))
|
||||
if fail:
|
||||
_log_skill_error("coord", f"msg purge: {fail} of {len(targets)} deletes failed",
|
||||
context=f"before={before}")
|
||||
|
||||
|
||||
def c_todo_add(a):
|
||||
text = a.text
|
||||
if a.text_file:
|
||||
@@ -236,6 +289,8 @@ def main():
|
||||
s.add_argument("--body-file"); s.add_argument("--project"); s.set_defaults(fn=c_msg_send)
|
||||
ib = m.add_parser("inbox"); ib.add_argument("--all", action="store_true"); ib.set_defaults(fn=c_msg_inbox)
|
||||
rd = m.add_parser("read"); rd.add_argument("id"); rd.set_defaults(fn=c_msg_read)
|
||||
pg = m.add_parser("purge"); pg.add_argument("--before", required=True); pg.add_argument("--to")
|
||||
pg.add_argument("--yes", action="store_true"); pg.set_defaults(fn=c_msg_purge)
|
||||
|
||||
t = sub.add_parser("todo").add_subparsers(dest="sub", required=True)
|
||||
ta = t.add_parser("add"); ta.add_argument("text", nargs="?"); ta.add_argument("--text-file")
|
||||
|
||||
Reference in New Issue
Block a user