Browse Source

experiment: Add alias command for directory renames, update SKILL.md

Documents all new commands (reply, broadcast, search, status, --urgent).
Alias command updates from_project/to_project in all historical messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0xDarkMatter 1 week ago
parent
commit
5fd782c15c
3 changed files with 123 additions and 53 deletions
  1. 63 53
      skills/agentmail/SKILL.md
  2. 25 0
      skills/agentmail/scripts/mail-db.sh
  3. 35 0
      skills/agentmail/scripts/test-mail.sh

+ 63 - 53
skills/agentmail/SKILL.md

@@ -17,7 +17,7 @@ Inter-session messaging for Claude Code. Sessions running in different project d
 Session A (claude-mods)    Session B (some-api)     Session C (frontend)
     |                          |                        |
     +-- check-mail hook -------+-- check-mail hook -----+-- check-mail hook
-    |   (PreToolUse, silent    |                        |
+    |   (PreToolUse, silent    |   (10s cooldown)       |
     |    when empty)           |                        |
     |                          |                        |
     +-- /mail send some-api ---+--> unread message -----+
@@ -32,98 +32,106 @@ Project name = `basename` of current working directory. No configuration needed.
 - `X:\Forge\claude-mods` -> `claude-mods`
 - `X:\Forge\some-api` -> `some-api`
 
-## Commands
+If a project directory is renamed, use the `alias` command to link old and new names:
 
-All commands use the helper script at `skills/agentmail/scripts/mail-db.sh`.
+```bash
+bash skills/agentmail/scripts/mail-db.sh alias "old-name" "new-name"
+```
 
-### Check for Mail
+## Commands
 
-The `check-mail.sh` hook runs automatically on every tool call. When unread messages exist, it outputs a notification. No action needed from user or assistant.
+All commands use the helper script at `skills/agentmail/scripts/mail-db.sh`.
 
-To manually check:
+### Send a Message
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh count
+bash skills/agentmail/scripts/mail-db.sh send "<target-project>" "<subject>" "<body>"
 ```
 
-### Read Messages
-
-Read all unread messages and mark them as read:
+Send with urgent priority (highlighted in hook notifications):
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh read
+bash skills/agentmail/scripts/mail-db.sh send --urgent "<target-project>" "<subject>" "<body>"
 ```
 
-Read a specific message by ID:
+### Read Messages
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh read 42
+bash skills/agentmail/scripts/mail-db.sh read          # All unread, mark as read
+bash skills/agentmail/scripts/mail-db.sh read 42        # Single message by ID
 ```
 
-### Send a Message
+### Reply
+
+Reply to a message - automatically addresses the sender with Re: prefix:
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh send "<target-project>" "<subject>" "<body>"
+bash skills/agentmail/scripts/mail-db.sh reply <message-id> "<body>"
 ```
 
-**Examples:**
-
-```bash
-# Simple notification
-bash skills/agentmail/scripts/mail-db.sh send "some-api" "Auth ready" "OAuth2 endpoints are implemented and tested on branch feature/oauth2"
+### Broadcast
 
-# Request for action
-bash skills/agentmail/scripts/mail-db.sh send "frontend" "API contract changed" "The /api/users endpoint now returns {data: User[], meta: {total: number}} instead of a flat array. See commit abc123."
+Send to all known projects (except self):
 
-# Broadcast to multiple projects
-for project in frontend some-api; do
-  bash skills/agentmail/scripts/mail-db.sh send "$project" "Main is broken" "Do not merge until fix lands - CI is red"
-done
+```bash
+bash skills/agentmail/scripts/mail-db.sh broadcast "<subject>" "<body>"
 ```
 
-### List Messages
+### Search
 
-Show recent messages (read and unread):
+Find messages by keyword in subject or body:
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh list        # Last 20
-bash skills/agentmail/scripts/mail-db.sh list 50      # Last 50
+bash skills/agentmail/scripts/mail-db.sh search "<keyword>"
 ```
 
-### List Known Projects
+### Status
 
-Show all projects that have sent or received mail:
+Inbox summary with per-project breakdown:
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh projects
+bash skills/agentmail/scripts/mail-db.sh status
 ```
 
-### Cleanup
-
-Delete old read messages:
+### Other Commands
 
 ```bash
-bash skills/agentmail/scripts/mail-db.sh clear        # Older than 7 days
-bash skills/agentmail/scripts/mail-db.sh clear 30      # Older than 30 days
+bash skills/agentmail/scripts/mail-db.sh count          # Unread count (number only)
+bash skills/agentmail/scripts/mail-db.sh unread          # List unread (brief)
+bash skills/agentmail/scripts/mail-db.sh list [N]        # Recent messages (default 20)
+bash skills/agentmail/scripts/mail-db.sh projects        # All known projects
+bash skills/agentmail/scripts/mail-db.sh clear [days]    # Delete read msgs older than N days
+bash skills/agentmail/scripts/mail-db.sh alias <old> <new>  # Rename project in all messages
+bash skills/agentmail/scripts/mail-db.sh init            # Initialize database
 ```
 
 ## Passive Notification (Hook)
 
-The `hooks/check-mail.sh` hook provides passive notification. It:
+The `hooks/check-mail.sh` hook provides passive notification:
 
-1. Runs on every tool call (PreToolUse matcher: `*`)
-2. Checks `~/.claude/mail.db` for unread messages where `to_project` matches current directory name
-3. Outputs nothing if inbox is empty (zero overhead)
-4. Shows count + preview of up to 3 messages when mail exists
+1. Runs on every tool call (PreToolUse, 10-second cooldown)
+2. Checks for unread messages matching current directory name
+3. Silent when inbox is empty
+4. Shows count + preview of up to 3 messages
+5. Highlights urgent messages with `[!]` prefix
 
-### Hook Output Example
+### Hook Output
 
 ```
-=== MAIL: 2 unread message(s) ===
+=== MAIL: 3 unread message(s) ===
   From: some-api  |  Auth endpoints ready
   From: frontend  |  Need updated types
+  ... and 1 more
+Use /mail to read messages.
+```
+
+Urgent messages:
+
+```
+=== URGENT MAIL: 2 unread (1 urgent) ===
+  [!] From: some-api  |  Production is down
+  From: frontend  |  Need updated types
 Use /mail to read messages.
-===
 ```
 
 ## When to Use
@@ -138,7 +146,7 @@ Use /mail to read messages.
 
 ## Database
 
-Single SQLite file at `~/.claude/mail.db`. Schema:
+Single SQLite file at `~/.claude/mail.db`. Not inside any git repo.
 
 ```sql
 CREATE TABLE messages (
@@ -148,17 +156,19 @@ CREATE TABLE messages (
     subject TEXT DEFAULT '',
     body TEXT NOT NULL,
     timestamp TEXT DEFAULT (datetime('now')),
-    read INTEGER DEFAULT 0
+    read INTEGER DEFAULT 0,
+    priority TEXT DEFAULT 'normal'
 );
 ```
 
-Database is auto-created on first use. Not inside any git repo - no gitignore needed.
+All user inputs are sanitized via SQL single-quote escaping. Numeric inputs (IDs, limits) are validated before use.
 
 ## Troubleshooting
 
 | Issue | Fix |
 |-------|-----|
-| `sqlite3: not found` | Install sqlite3 (ships with most OS installs, Git Bash on Windows) |
-| Hook not firing | Check hook is registered in `.claude/settings.json` or `.claude/settings.local.json` |
-| Wrong project name | Hook uses `basename $PWD` - ensure cwd is the project root |
-| Messages not arriving | Check `to_project` matches target's directory basename exactly |
+| `sqlite3: not found` | Ships with macOS, Linux, and Git Bash on Windows |
+| Hook not firing | Register in `.claude/settings.json` or `.claude/settings.local.json` |
+| Wrong project name | Uses `basename $PWD` - ensure cwd is project root |
+| Messages not arriving | `to_project` must match target's directory basename exactly |
+| Renamed directory | Use `alias` command to update old name to new name |

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

@@ -236,6 +236,29 @@ status() {
   fi
 }
 
+# Rename a project in all messages (for directory renames/moves)
+alias_project() {
+  local old_name="$1"
+  local new_name="$2"
+  if [ -z "$old_name" ] || [ -z "$new_name" ]; then
+    echo "Error: both old and new project names required" >&2
+    return 1
+  fi
+  init_db
+  local safe_old safe_new
+  safe_old=$(sql_escape "$old_name")
+  safe_new=$(sql_escape "$new_name")
+  local updated=0
+  local count
+  count=$(sqlite3 "$MAIL_DB" \
+    "UPDATE messages SET from_project='${safe_new}' WHERE from_project='${safe_old}'; SELECT changes();")
+  updated=$((updated + count))
+  count=$(sqlite3 "$MAIL_DB" \
+    "UPDATE messages SET to_project='${safe_new}' WHERE to_project='${safe_old}'; SELECT changes();")
+  updated=$((updated + count))
+  echo "Renamed '${old_name}' -> '${new_name}' in ${updated} message(s)"
+}
+
 # List all known projects (that have sent or received mail)
 list_projects() {
   init_db
@@ -256,6 +279,7 @@ case "${1:-help}" in
   broadcast)  broadcast "${2:-no subject}" "${3:?body required}" ;;
   search)     search "${2:?keyword required}" ;;
   status)     status ;;
+  alias)      alias_project "${2:?old name required}" "${3:?new name required}" ;;
   projects)   list_projects ;;
   help)
     echo "Usage: mail-db.sh <command> [args]"
@@ -273,6 +297,7 @@ case "${1:-help}" in
     echo "  broadcast <subj> <body> Send to all known projects"
     echo "  search <keyword>        Search messages by keyword"
     echo "  status                  Inbox summary"
+    echo "  alias <old> <new>       Rename project in all messages"
     echo "  projects                List known projects"
     ;;
   *)          echo "Unknown command: $1. Run with 'help' for usage." >&2; exit 1 ;;

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

@@ -428,6 +428,41 @@ result=$(bash "$MAIL_SCRIPT" status 2>&1)
 assert_contains "status shows 0 unread" "0 unread" "$result"
 
 echo ""
+echo "=== Alias (Rename) ==="
+
+# Setup: send messages with old project name
+bash "$MAIL_SCRIPT" send "old-project" "before rename" "testing alias" >/dev/null 2>&1
+bash "$MAIL_SCRIPT" send "claude-mods" "from old" "message from old name" >/dev/null 2>&1
+
+# T47: Alias renames in all messages
+result=$(bash "$MAIL_SCRIPT" alias "old-project" "new-project" 2>&1)
+assert_contains "alias reports rename" "Renamed" "$result"
+assert_contains "alias shows old name" "old-project" "$result"
+assert_contains "alias shows new name" "new-project" "$result"
+
+# T48: Old project name no longer appears
+result=$(bash "$MAIL_SCRIPT" projects)
+TOTAL=$((TOTAL + 1))
+if echo "$result" | grep -qF "old-project"; then
+  echo "FAIL: old project name still present after alias"
+  FAIL=$((FAIL + 1))
+else
+  echo "PASS: old project name removed after alias"
+  PASS=$((PASS + 1))
+fi
+
+# T49: New project name appears
+assert_contains "new project name present" "new-project" "$result"
+
+# T50: Alias with missing args fails
+result=$(bash "$MAIL_SCRIPT" alias "only-one" 2>&1)
+exit_code=$?
+assert_exit_code "alias with missing arg 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