Browse Source

experiment: Add reply command with Re: prefix and sender lookup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0xDarkMatter 1 week ago
parent
commit
2ff5062169
2 changed files with 65 additions and 0 deletions
  1. 36 0
      skills/agentmail/scripts/mail-db.sh
  2. 29 0
      skills/agentmail/scripts/test-mail.sh

+ 36 - 0
skills/agentmail/scripts/mail-db.sh

@@ -127,6 +127,40 @@ clear_old() {
   echo "Cleared ${deleted} read messages older than ${days} days"
 }
 
+# Reply to a message by ID
+reply() {
+  local msg_id="$1"
+  local body="$2"
+  if ! [[ "$msg_id" =~ ^[0-9]+$ ]]; then
+    echo "Error: message ID must be numeric" >&2
+    return 1
+  fi
+  if [ -z "$body" ]; then
+    echo "Error: reply body cannot be empty" >&2
+    return 1
+  fi
+  init_db
+  # Get original sender and subject
+  local orig
+  orig=$(sqlite3 -separator '|' "$MAIL_DB" "SELECT from_project, subject FROM messages WHERE id=${msg_id};")
+  if [ -z "$orig" ]; then
+    echo "Error: message #${msg_id} not found" >&2
+    return 1
+  fi
+  local orig_from orig_subject
+  orig_from=$(echo "$orig" | cut -d'|' -f1)
+  orig_subject=$(echo "$orig" | cut -d'|' -f2)
+  local from_project
+  from_project=$(sql_escape "$(get_project)")
+  local safe_to safe_subject safe_body
+  safe_to=$(sql_escape "$orig_from")
+  safe_subject=$(sql_escape "Re: ${orig_subject}")
+  safe_body=$(sql_escape "$body")
+  sqlite3 "$MAIL_DB" \
+    "INSERT INTO messages (from_project, to_project, subject, body) VALUES ('${from_project}', '${safe_to}', '${safe_subject}', '${safe_body}');"
+  echo "Replied to ${orig_from}: Re: ${orig_subject}"
+}
+
 # List all known projects (that have sent or received mail)
 list_projects() {
   init_db
@@ -141,6 +175,7 @@ case "${1:-help}" in
   unread)     list_unread ;;
   read)       if [ -n "${2:-}" ]; then read_one "$2"; else read_mail; fi ;;
   send)       send "${2:?to_project required}" "${3:-no subject}" "${4:?body required}" ;;
+  reply)      reply "${2:?message_id required}" "${3:?body required}" ;;
   list)       list_all "${2:-20}" ;;
   clear)      clear_old "${2:-7}" ;;
   projects)   list_projects ;;
@@ -153,6 +188,7 @@ case "${1:-help}" in
     echo "  unread                  List unread messages (brief)"
     echo "  read [id]               Read messages and mark as read"
     echo "  send <to> <subj> <body> Send a message"
+    echo "  reply <id> <body>       Reply to a message"
     echo "  list [limit]            List recent messages (default 20)"
     echo "  clear [days]            Clear read messages older than N days"
     echo "  projects                List known projects"

+ 29 - 0
skills/agentmail/scripts/test-mail.sh

@@ -320,6 +320,35 @@ assert_contains "empty subject accepted" "Sent to claude-mods" "$result"
 bash "$MAIL_SCRIPT" read >/dev/null 2>&1
 
 echo ""
+echo "=== Reply ==="
+
+# T38: Reply to a message
+bash "$MAIL_SCRIPT" send "claude-mods" "Original msg" "Please reply" >/dev/null 2>&1
+msg_id=$(sqlite3 "$MAIL_DB" "SELECT id FROM messages WHERE subject='Original msg' AND read=0 LIMIT 1;")
+bash "$MAIL_SCRIPT" read "$msg_id" >/dev/null 2>&1
+result=$(bash "$MAIL_SCRIPT" reply "$msg_id" "Here is my reply" 2>&1)
+assert_contains "reply succeeds" "Replied to claude-mods" "$result"
+assert_contains "reply has Re: prefix" "Re: Original msg" "$result"
+
+# T39: Reply to nonexistent message
+result=$(bash "$MAIL_SCRIPT" reply 99999 "reply to nothing" 2>&1)
+exit_code=$?
+assert_exit_code "reply to nonexistent fails" "1" "$exit_code"
+
+# T40: Reply with empty body
+result=$(bash "$MAIL_SCRIPT" reply "$msg_id" "" 2>&1)
+exit_code=$?
+assert_exit_code "reply with empty body fails" "1" "$exit_code"
+
+# T41: Reply with non-numeric ID
+result=$(bash "$MAIL_SCRIPT" reply "abc" "body" 2>&1)
+exit_code=$?
+assert_exit_code "reply with non-numeric ID fails" "1" "$exit_code"
+
+# Clean up
+bash "$MAIL_SCRIPT" read >/dev/null 2>&1
+
+echo ""
 echo "=== Performance ==="
 
 # T38: Hook cooldown - second call within cooldown is silent even with mail