imsg

SKILL.md

iMessage/SMS CLI for macOS

A command-line interface for interacting with macOS Messages.app using AppleScript/osascript. List conversations, view message history, monitor for new messages, and send iMessages or SMS.

Prerequisites

System Requirements

  1. macOS - This skill only works on macOS (Messages.app is macOS-only)
  2. Messages.app configured - Must be signed into iCloud or have carrier SMS set up
  3. Automation permissions - Terminal/Claude must have permission to control Messages.app

Granting Permissions

When first running these commands, macOS will prompt for automation permission:

  1. Go to System Preferences > Security & Privacy > Privacy > Automation
  2. Enable Terminal (or your terminal app) to control Messages
  3. If using Claude Code, ensure the parent process has automation permissions

You may also need Full Disk Access to read the Messages database directly:

  1. Go to System Preferences > Security & Privacy > Privacy > Full Disk Access
  2. Add your terminal application

Commands

1. List Chats

List all conversations/chat threads.

# List all chats using AppleScript
osascript -e 'tell application "Messages"
    set chatList to {}
    repeat with aChat in chats
        set chatName to name of aChat
        set chatId to id of aChat
        set end of chatList to chatId & " | " & chatName
    end repeat
    return chatList
end tell'

Alternative: Query the Messages database directly (requires Full Disk Access)

# Get recent conversations from SQLite database
sqlite3 ~/Library/Messages/chat.db "
SELECT
    c.guid,
    c.chat_identifier,
    c.display_name,
    datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as last_message_date
FROM chat c
LEFT JOIN chat_message_join cmj ON c.ROWID = cmj.chat_id
LEFT JOIN message m ON cmj.message_id = m.ROWID
GROUP BY c.ROWID
ORDER BY m.date DESC
LIMIT 20;
"

Output Format:

iMessage;-;+1234567890 | +1234567890
iMessage;-;chat123456 | Group Chat Name
SMS;-;+0987654321 | Contact Name

2. Get Message History

View message history for a specific conversation.

Using AppleScript (limited to recent messages):

# Get messages from a specific chat by participant
osascript -e 'tell application "Messages"
    set targetBuddy to "+1234567890"
    set msgList to {}
    repeat with aChat in chats
        if (count of participants of aChat) = 1 then
            set participant to item 1 of participants of aChat
            if handle of participant contains targetBuddy then
                -- Note: AppleScript has limited access to message history
                set end of msgList to "Chat found: " & name of aChat
            end if
        end if
    end repeat
    return msgList
end tell'

Using SQLite (recommended for full history):

# Get message history for a specific contact/chat
PHONE_NUMBER="+1234567890"

sqlite3 ~/Library/Messages/chat.db "
SELECT
    datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as date,
    CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE h.id END as sender,
    m.text
FROM message m
LEFT JOIN handle h ON m.handle_id = h.ROWID
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
WHERE c.chat_identifier LIKE '%${PHONE_NUMBER}%'
    OR h.id LIKE '%${PHONE_NUMBER}%'
ORDER BY m.date DESC
LIMIT 50;
"

Get messages from a group chat:

CHAT_ID="chat123456789"

sqlite3 ~/Library/Messages/chat.db "
SELECT
    datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as date,
    CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE COALESCE(h.id, 'Unknown') END as sender,
    m.text
FROM message m
LEFT JOIN handle h ON m.handle_id = h.ROWID
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
WHERE c.guid = '${CHAT_ID}'
ORDER BY m.date DESC
LIMIT 50;
"

Output Format:

2024-01-15 14:32:15|Me|Hey, are you free for lunch?
2024-01-15 14:35:22|+1234567890|Sure! Where do you want to go?
2024-01-15 14:36:01|Me|How about the place on Main St?

3. Watch for New Messages

Monitor for incoming messages in real-time.

Polling approach using SQLite:

#!/bin/bash
# watch_messages.sh - Poll for new messages every 5 seconds

LAST_ROWID=$(sqlite3 ~/Library/Messages/chat.db "SELECT MAX(ROWID) FROM message;")

while true; do
    sleep 5

    NEW_MESSAGES=$(sqlite3 ~/Library/Messages/chat.db "
    SELECT
        datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as date,
        CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE COALESCE(h.id, 'Unknown') END as sender,
        c.chat_identifier,
        m.text
    FROM message m
    LEFT JOIN handle h ON m.handle_id = h.ROWID
    LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
    LEFT JOIN chat c ON cmj.chat_id = c.ROWID
    WHERE m.ROWID > ${LAST_ROWID}
    ORDER BY m.date ASC;
    ")

    if [ -n "$NEW_MESSAGES" ]; then
        echo "=== New Messages ==="
        echo "$NEW_MESSAGES"
        echo "===================="
    fi

    LAST_ROWID=$(sqlite3 ~/Library/Messages/chat.db "SELECT MAX(ROWID) FROM message;")
done

One-time check for unread/recent messages:

# Get messages from the last hour
sqlite3 ~/Library/Messages/chat.db "
SELECT
    datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as date,
    CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE COALESCE(h.id, 'Unknown') END as sender,
    c.chat_identifier,
    m.text
FROM message m
LEFT JOIN handle h ON m.handle_id = h.ROWID
LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
LEFT JOIN chat c ON cmj.chat_id = c.ROWID
WHERE m.date > (strftime('%s', 'now', '-1 hour') - strftime('%s', '2001-01-01')) * 1000000000
ORDER BY m.date DESC;
"

4. Send Message

Send an iMessage or SMS to a contact or group.

Send to individual (phone number or email):

# Send iMessage/SMS to a phone number
osascript -e 'tell application "Messages"
    set targetService to 1st service whose service type = iMessage
    set targetBuddy to buddy "+1234567890" of targetService
    send "Hello! This is a test message." to targetBuddy
end tell'

Alternative syntax with error handling:

# More robust send with service detection
osascript <<'EOF'
tell application "Messages"
    set targetPhone to "+1234567890"
    set messageText to "Hello! This is a test message."

    -- Try iMessage first, fall back to SMS
    try
        set targetService to 1st service whose service type = iMessage
        set targetBuddy to buddy targetPhone of targetService
        send messageText to targetBuddy
        return "Sent via iMessage"
    on error
        try
            set targetService to 1st service whose service type = SMS
            set targetBuddy to buddy targetPhone of targetService
            send messageText to targetBuddy
            return "Sent via SMS"
        on error errMsg
            return "Error: " & errMsg
        end try
    end try
end tell
EOF

Send to a group chat:

# Send to existing group chat by name
osascript -e 'tell application "Messages"
    set targetChat to chat "Family Group"
    send "Hello everyone!" to targetChat
end tell'

Send to group chat by ID:

# Send using chat ID (more reliable for groups)
osascript <<'EOF'
tell application "Messages"
    set chatId to "iMessage;+;chat123456789"
    repeat with aChat in chats
        if id of aChat is chatId then
            send "Hello from the script!" to aChat
            return "Message sent"
        end if
    end repeat
    return "Chat not found"
end tell
EOF

Send with variables (Bash integration):

# Function to send a message
send_imessage() {
    local recipient="$1"
    local message="$2"

    # Escape special characters for AppleScript
    message=$(echo "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')

    osascript <<EOF
tell application "Messages"
    set targetService to 1st service whose service type = iMessage
    set targetBuddy to buddy "$recipient" of targetService
    send "$message" to targetBuddy
end tell
EOF
}

# Usage
send_imessage "+1234567890" "Hello! How are you?"

Complete CLI Script

Here's a complete CLI script that combines all functionality:

#!/bin/bash
# imsg - iMessage/SMS CLI for macOS
# Usage: imsg <command> [options]

MESSAGES_DB="$HOME/Library/Messages/chat.db"

# Check if Messages database exists
check_db() {
    if [ ! -f "$MESSAGES_DB" ]; then
        echo "Error: Messages database not found at $MESSAGES_DB"
        echo "Ensure you have Full Disk Access enabled for Terminal"
        exit 1
    fi
}

# List all chats
list_chats() {
    check_db
    echo "Recent Conversations:"
    echo "====================="
    sqlite3 "$MESSAGES_DB" "
    SELECT
        c.chat_identifier as 'ID',
        COALESCE(c.display_name, c.chat_identifier) as 'Name',
        datetime(MAX(m.date)/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as 'Last Message'
    FROM chat c
    LEFT JOIN chat_message_join cmj ON c.ROWID = cmj.chat_id
    LEFT JOIN message m ON cmj.message_id = m.ROWID
    GROUP BY c.ROWID
    ORDER BY MAX(m.date) DESC
    LIMIT 20;
    " -column -header
}

# Get message history
get_history() {
    local identifier="$1"
    local limit="${2:-50}"

    check_db
    echo "Message History for: $identifier"
    echo "================================="
    sqlite3 "$MESSAGES_DB" "
    SELECT
        datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as 'Time',
        CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE COALESCE(h.id, 'Unknown') END as 'From',
        SUBSTR(m.text, 1, 100) as 'Message'
    FROM message m
    LEFT JOIN handle h ON m.handle_id = h.ROWID
    LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
    LEFT JOIN chat c ON cmj.chat_id = c.ROWID
    WHERE c.chat_identifier LIKE '%${identifier}%'
        OR h.id LIKE '%${identifier}%'
    ORDER BY m.date DESC
    LIMIT ${limit};
    " -column -header
}

# Watch for new messages
watch_messages() {
    local interval="${1:-5}"

    check_db
    echo "Watching for new messages (Ctrl+C to stop)..."

    local last_rowid=$(sqlite3 "$MESSAGES_DB" "SELECT MAX(ROWID) FROM message;")

    while true; do
        sleep "$interval"

        local new_messages=$(sqlite3 "$MESSAGES_DB" "
        SELECT
            datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as date,
            CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE COALESCE(h.id, 'Unknown') END as sender,
            c.chat_identifier,
            m.text
        FROM message m
        LEFT JOIN handle h ON m.handle_id = h.ROWID
        LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
        LEFT JOIN chat c ON cmj.chat_id = c.ROWID
        WHERE m.ROWID > ${last_rowid}
        ORDER BY m.date ASC;
        ")

        if [ -n "$new_messages" ]; then
            echo ""
            echo "=== New Message $(date) ==="
            echo "$new_messages"
        fi

        last_rowid=$(sqlite3 "$MESSAGES_DB" "SELECT MAX(ROWID) FROM message;")
    done
}

# Send a message
send_message() {
    local recipient="$1"
    local message="$2"

    if [ -z "$recipient" ] || [ -z "$message" ]; then
        echo "Usage: imsg send <recipient> <message>"
        echo "  recipient: phone number (e.g., +1234567890) or email"
        echo "  message: text to send"
        exit 1
    fi

    # Escape special characters for AppleScript
    local escaped_message=$(echo "$message" | sed 's/\\/\\\\/g; s/"/\\"/g')

    osascript <<EOF
tell application "Messages"
    set targetPhone to "$recipient"
    set messageText to "$escaped_message"

    try
        set targetService to 1st service whose service type = iMessage
        set targetBuddy to buddy targetPhone of targetService
        send messageText to targetBuddy
        return "Sent via iMessage to $recipient"
    on error
        try
            set targetService to 1st service whose service type = SMS
            set targetBuddy to buddy targetPhone of targetService
            send messageText to targetBuddy
            return "Sent via SMS to $recipient"
        on error errMsg
            return "Error: " & errMsg
        end try
    end try
end tell
EOF
}

# Get recent unread/new messages
get_recent() {
    local minutes="${1:-60}"

    check_db
    echo "Messages from the last $minutes minutes:"
    echo "========================================="
    sqlite3 "$MESSAGES_DB" "
    SELECT
        datetime(m.date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch', 'localtime') as 'Time',
        CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE COALESCE(h.id, 'Unknown') END as 'From',
        COALESCE(c.display_name, c.chat_identifier) as 'Chat',
        SUBSTR(m.text, 1, 80) as 'Message'
    FROM message m
    LEFT JOIN handle h ON m.handle_id = h.ROWID
    LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
    LEFT JOIN chat c ON cmj.chat_id = c.ROWID
    WHERE m.date > (strftime('%s', 'now', '-${minutes} minutes') - strftime('%s', '2001-01-01')) * 1000000000
    ORDER BY m.date DESC;
    " -column -header
}

# Main command router
case "$1" in
    list|chats)
        list_chats
        ;;
    history|get)
        get_history "$2" "$3"
        ;;
    watch)
        watch_messages "$2"
        ;;
    send)
        send_message "$2" "$3"
        ;;
    recent|new)
        get_recent "$2"
        ;;
    help|--help|-h)
        echo "imsg - iMessage/SMS CLI for macOS"
        echo ""
        echo "Usage: imsg <command> [options]"
        echo ""
        echo "Commands:"
        echo "  list              List all conversations"
        echo "  history <id>      Get message history for a contact/chat"
        echo "  recent [minutes]  Get messages from the last N minutes (default: 60)"
        echo "  watch [interval]  Watch for new messages (default interval: 5 seconds)"
        echo "  send <to> <msg>   Send a message to a phone number or email"
        echo "  help              Show this help message"
        echo ""
        echo "Examples:"
        echo "  imsg list"
        echo "  imsg history +1234567890"
        echo "  imsg history \"Family Group\""
        echo "  imsg recent 30"
        echo "  imsg watch 10"
        echo "  imsg send +1234567890 \"Hello there!\""
        ;;
    *)
        echo "Unknown command: $1"
        echo "Run 'imsg help' for usage information"
        exit 1
        ;;
esac

Usage Examples

List Recent Conversations

User: /imsg list

Claude Response:

Recent Conversations:
=====================
ID                          | Name              | Last Message
--------------------------- | ----------------- | -------------------
+1234567890                 | +1234567890       | 2024-01-15 14:36:01
iMessage;+;chat123456789    | Family Group      | 2024-01-15 12:22:45
john@example.com            | John Smith        | 2024-01-14 09:15:33

Get Chat History

User: /imsg history +1234567890

Claude Response:

Message History for: +1234567890
=================================
Time                | From        | Message
------------------- | ----------- | -----------------------------------------
2024-01-15 14:36:01 | Me          | How about the place on Main St?
2024-01-15 14:35:22 | +1234567890 | Sure! Where do you want to go?
2024-01-15 14:32:15 | Me          | Hey, are you free for lunch?

Send a Message

User: /imsg send +1234567890 "Hey! Running 10 mins late"

Claude Response:

Sent via iMessage to +1234567890

Check Recent Messages

User: /imsg recent 30

Claude Response:

Messages from the last 30 minutes:
=========================================
Time                | From        | Chat          | Message
------------------- | ----------- | ------------- | ----------------------------
2024-01-15 14:45:12 | +1234567890 | +1234567890   | No worries, see you soon!
2024-01-15 14:36:01 | Me          | +1234567890   | How about the place on Main St?

Troubleshooting

Common Issues

Issue Solution
"Error: Messages database not found" Enable Full Disk Access for Terminal in System Preferences
"Automation permission denied" Go to System Preferences > Security & Privacy > Automation and enable Terminal
"buddy not found" Ensure the phone number includes country code (e.g., +1 for US)
"service not available" Check that Messages.app is signed in and working
"database is locked" Close Messages.app or wait a moment and retry

Permission Setup

  1. Full Disk Access (required for reading message history):

    System Preferences > Security & Privacy > Privacy > Full Disk Access
    

    Add: Terminal.app (or iTerm, Warp, etc.)

  2. Automation (required for sending messages):

    System Preferences > Security & Privacy > Privacy > Automation
    

    Enable: Terminal > Messages

Verifying Setup

# Test database access
sqlite3 ~/Library/Messages/chat.db "SELECT COUNT(*) FROM message;"

# Test AppleScript access
osascript -e 'tell application "Messages" to count of chats'

Security Considerations

  • Local only: This skill only works on the local macOS machine
  • No network access: Messages are read/sent locally via Messages.app
  • Permission gated: macOS permissions prevent unauthorized access
  • No credential storage: Uses system-level iCloud/carrier authentication

Limitations

  1. macOS only - Will not work on Linux, Windows, or remote servers
  2. Local execution - Must run on the machine with Messages.app
  3. iMessage/SMS only - Does not support WhatsApp, Signal, etc.
  4. Read receipts - Cannot access read receipt status via CLI
  5. Attachments - Limited support for images/files in CLI output
  6. Group creation - Cannot create new group chats via AppleScript

Integration with ClawdBot

Cron Job for Message Monitoring

{
  "id": "imsg-morning-summary",
  "name": "Morning Message Summary",
  "description": "Summarize overnight messages",
  "enabled": true,
  "schedule": {
    "kind": "cron",
    "value": "0 8 * * *",
    "tz": "America/New_York"
  },
  "sessionTarget": "isolated",
  "payload": {
    "kind": "agentTurn",
    "message": "Use the imsg skill to check for messages from the last 12 hours. Summarize any important messages that need attention.",
    "deliver": true,
    "channel": "telegram",
    "to": "302137836"
  }
}

Quick Send from Telegram

When a user says "text John that I'm running late", Claude can:

  1. Look up John's phone number in contacts
  2. Use imsg send to send the message
  3. Confirm delivery

Related Skills

  • keep-in-touch - Contact relationship management
  • email-manager - Email triage and management
  • note - Quick capture for follow-up items
  • remind - Set reminders for message follow-ups

Keywords

imessage, sms, text message, messages, macos, applescript, osascript, chat, conversation, send text, messaging, iphone messages

Weekly Installs
1
First Seen
Feb 6, 2026
Installed on
replit1
openclaw1
opencode1
cursor1
codex1
claude-code1