mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-10-02 01:34:57 +08:00
imessage: surface every inbound image, not just the first
An iMessage with several photos arrived with only the first one in the channel tag: handleInbound took the first image attachment and stopped. Collect every image attachment instead (same filters as before) and pass each path in the notification meta as image_path, image_path_2, image_path_3, ... so the model can Read all of them. A single image still produces exactly image_path, so existing behaviour is unchanged. Numbered keys rather than an array or a joined list: Claude Code accepts only string values in channel meta and renders each key as an attribute on the <channel> tag, so keys must be plain identifiers; one key per path also avoids inventing a delimiter for sender-controlled filenames. The attachments query gains ORDER BY a.ROWID so the numbering follows attachment ROWID (creation) order. Instructions and README updated to match; version 0.1.0 -> 0.2.0 in plugin.json and package.json. Known limits, unchanged by this patch: attachments still downloading when the message row is first polled (filename NULL) are skipped and, since the poll watermark has already passed the message, never picked up later; HEIC is passed through unconverted; attachments with a NULL mime_type still pass the image filter; no cap on images per message.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "imessage",
|
||||
"description": "iMessage channel for Claude Code \u2014 reads chat.db directly, sends via AppleScript. Built-in access control; manage pairing, allowlists, and policy via /imessage:access.",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"keywords": [
|
||||
"imessage",
|
||||
"messaging",
|
||||
|
||||
@@ -55,7 +55,7 @@ Handles are phone numbers (`+15551234567`) or Apple ID emails (`them@icloud.com`
|
||||
| **Inbound** | Polls `chat.db` once a second for `ROWID > watermark`. Watermark initializes to `MAX(ROWID)` at boot — old messages aren't replayed on restart. |
|
||||
| **Outbound** | `osascript` with `tell application "Messages" to send …`. Text and chat GUID pass through argv so there's no escaping footgun. |
|
||||
| **History & search** | Direct SQLite queries against `chat.db`. Full history — not just messages since the server started. |
|
||||
| **Attachments** | `chat.db` stores absolute filesystem paths. The first inbound image per message is surfaced to the assistant as a local path it can `Read`. Outbound attachments send as separate messages after the text. |
|
||||
| **Attachments** | `chat.db` stores absolute filesystem paths. Every inbound image in a message is surfaced to the assistant as a local path it can `Read` (`image_path`, `image_path_2`, …). Outbound attachments send as separate messages after the text. |
|
||||
|
||||
## Environment variables
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-channel-imessage",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"bin": "./server.ts",
|
||||
|
||||
@@ -168,6 +168,7 @@ const qAttachments = db.query<AttRow, [number]>(`
|
||||
FROM attachment a
|
||||
JOIN message_attachment_join maj ON maj.attachment_id = a.ROWID
|
||||
WHERE maj.message_id = ?
|
||||
ORDER BY a.ROWID
|
||||
`)
|
||||
|
||||
// Your own addresses, from message.account ("E:you@icloud.com" / "p:+1555...")
|
||||
@@ -557,7 +558,7 @@ const mcp = new Server(
|
||||
instructions: [
|
||||
'The sender reads iMessage, not this session. Anything you want them to see must go through the reply tool — your transcript output never reaches their chat.',
|
||||
'',
|
||||
'Messages from iMessage arrive as <channel source="imessage" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file — it is an image the sender attached. Reply with the reply tool — pass chat_id back.',
|
||||
'Messages from iMessage arrive as <channel source="imessage" chat_id="..." message_id="..." user="..." ts="...">. If the tag has image_path attributes (image_path, image_path_2, ...), Read each file in order — they are images the sender attached. Reply with the reply tool — pass chat_id back.',
|
||||
'',
|
||||
'reply accepts file paths (files: ["/abs/path.png"]) for attachments.',
|
||||
'',
|
||||
@@ -844,20 +845,25 @@ function handleInbound(r: Row): void {
|
||||
}
|
||||
|
||||
// attachment.filename is an absolute path (sometimes tilde-prefixed) —
|
||||
// already on disk, no download. Include the first image inline.
|
||||
let imagePath: string | undefined
|
||||
// already on disk, no download. Collect every image, in attachment ROWID order.
|
||||
const imagePaths: string[] = []
|
||||
if (hasAttachments) {
|
||||
for (const att of qAttachments.all(r.rowid)) {
|
||||
if (!att.filename) continue
|
||||
if (att.mime_type && !att.mime_type.startsWith('image/')) continue
|
||||
imagePath = expandTilde(att.filename)
|
||||
break
|
||||
imagePaths.push(expandTilde(att.filename))
|
||||
}
|
||||
}
|
||||
|
||||
// image_path goes in meta only — an in-content "[image attached — read: PATH]"
|
||||
// Image paths go in meta only — an in-content "[image attached — read: PATH]"
|
||||
// annotation is forgeable by any allowlisted sender typing that string.
|
||||
const content = text || (imagePath ? '(image)' : '')
|
||||
// One key per image: image_path, image_path_2, image_path_3, ... (Claude
|
||||
// Code accepts only string meta values, so no array).
|
||||
const imageMeta: Record<string, string> = {}
|
||||
imagePaths.forEach((p, i) => {
|
||||
imageMeta[i === 0 ? 'image_path' : `image_path_${i + 1}`] = p
|
||||
})
|
||||
const content = text || (imagePaths.length ? '(image)' : '')
|
||||
|
||||
void mcp.notification({
|
||||
method: 'notifications/claude/channel',
|
||||
@@ -868,7 +874,7 @@ function handleInbound(r: Row): void {
|
||||
message_id: r.guid,
|
||||
user: sender,
|
||||
ts: appleDate(r.date).toISOString(),
|
||||
...(imagePath ? { image_path: imagePath } : {}),
|
||||
...imageMeta,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user