imsg
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
- macOS - This skill only works on macOS (Messages.app is macOS-only)
- Messages.app configured - Must be signed into iCloud or have carrier SMS set up
- Automation permissions - Terminal/Claude must have permission to control Messages.app
Granting Permissions
When first running these commands, macOS will prompt for automation permission:
- Go to System Preferences > Security & Privacy > Privacy > Automation
- Enable Terminal (or your terminal app) to control Messages
- If using Claude Code, ensure the parent process has automation permissions
You may also need Full Disk Access to read the Messages database directly:
- Go to System Preferences > Security & Privacy > Privacy > Full Disk Access
- 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
-
Full Disk Access (required for reading message history):
System Preferences > Security & Privacy > Privacy > Full Disk AccessAdd: Terminal.app (or iTerm, Warp, etc.)
-
Automation (required for sending messages):
System Preferences > Security & Privacy > Privacy > AutomationEnable: 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
- macOS only - Will not work on Linux, Windows, or remote servers
- Local execution - Must run on the machine with Messages.app
- iMessage/SMS only - Does not support WhatsApp, Signal, etc.
- Read receipts - Cannot access read receipt status via CLI
- Attachments - Limited support for images/files in CLI output
- 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:
- Look up John's phone number in contacts
- Use
imsg sendto send the message - 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