Skip to content

feat(cli): add send-attachment and send-remote-attachment commands#1707

Open
yewreeka wants to merge 1 commit intomainfrom
jarod/send-attachment
Open

feat(cli): add send-attachment and send-remote-attachment commands#1707
yewreeka wants to merge 1 commit intomainfrom
jarod/send-attachment

Conversation

@yewreeka
Copy link

Summary

Add file attachment support to the XMTP CLI with two new commands and a pluggable upload provider system.

New Commands

conversation send-attachment — Read a file from disk and send it to a conversation.

  • Small files (≤1MB) are sent inline as Attachment messages
  • Large files are automatically encrypted and uploaded via the configured upload provider, then sent as RemoteAttachment messages
  • --encrypt flag for manual workflows: encrypts the file and outputs decryption keys without sending
  • --remote flag to force remote upload even for small files
  • Auto-detects MIME type from file extension (26 types), with --mime-type override

conversation send-remote-attachment — Send a reference to a pre-uploaded encrypted file with the required decryption keys (content-digest, secret, salt, nonce, content-length).

Upload Provider System

Pluggable provider interface for uploading encrypted attachments. Providers are configured via env vars or per-command flags:

XMTP_UPLOAD_PROVIDER=pinata
XMTP_UPLOAD_PROVIDER_TOKEN=<jwt>
XMTP_UPLOAD_PROVIDER_GATEWAY=https://your-gateway.mypinata.cloud  # optional

Or per-command:

xmtp conversation send-attachment <id> ./photo.jpg \
  --upload-provider pinata --upload-provider-token <jwt>

Built-in provider: Pinata (IPFS) — Uploads encrypted payload to IPFS via Pinata's pinning API, returns a public gateway URL.

Adding new providers (S3, Cloudflare R2, etc.) requires adding a single factory function to PROVIDER_FACTORIES in upload.ts.

Files Changed

File Description
src/commands/conversation/send-attachment.ts New command: smart send with inline/remote auto-detection
src/commands/conversation/send-remote-attachment.ts New command: send pre-uploaded encrypted file by URL
src/utils/upload.ts Upload provider interface + Pinata implementation
src/utils/config.ts 3 new config fields for upload provider settings
README.md Attachments section + config table updates
SKILL.md Send Attachments section + env var table updates

Testing

  • --encrypt mode tested end-to-end (file encryption + key output)
  • Pinata upload tested end-to-end (12KB and 2.9MB files, both successful)
  • Uploaded files verified accessible via IPFS gateway
  • Zero type errors, zero lint errors, prettier-formatted

@yewreeka yewreeka requested review from a team as code owners February 12, 2026 02:59
@changeset-bot
Copy link

changeset-bot bot commented Feb 12, 2026

🦋 Changeset detected

Latest commit: b1a83d6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@xmtp/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Feb 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xmtp-chat-api-service Ready Ready Preview, Comment Feb 12, 2026 4:43am
xmtp-js-xmtp-chat Ready Ready Preview, Comment Feb 12, 2026 4:43am

Request Review

@macroscopeapp
Copy link

macroscopeapp bot commented Feb 12, 2026

Add xmtp-cli commands conversation send-attachment and conversation send-remote-attachment to send inline or remote attachments with a 1,000,000‑byte inline threshold

Introduce attachment sending and downloading in the CLI, add upload provider config and Pinata integration, and update docs. Implement remote upload selection via getUploadProvider, MIME helpers, and new flags for encryption and raw download.

📍Where to Start

Start with the command flow in packages/xmtp-cli/src/commands/conversation/send-attachment.ts, then review provider selection in packages/xmtp-cli/src/utils/upload.ts and message handling in packages/xmtp-cli/src/commands/conversation/download-attachment.ts.


Macroscope summarized b1a83d6.

Add file attachment support to the CLI:

- conversation send-attachment: reads a file from disk and sends it.
  Small files (≤1MB) are sent inline; large files are automatically
  encrypted and uploaded via a configured upload provider, then sent
  as a remote attachment.

- conversation send-remote-attachment: sends a reference to a
  pre-uploaded encrypted file with the required decryption keys.

- Pluggable upload provider system with Pinata (IPFS) as the first
  built-in provider. Configured via XMTP_UPLOAD_PROVIDER and
  XMTP_UPLOAD_PROVIDER_TOKEN env vars or per-command flags.

- --encrypt flag for manual workflows: encrypts the file and outputs
  decryption keys without sending, so users can upload to their own
  hosting and use send-remote-attachment.

- Auto-detects MIME type from file extension (26 types supported),
  with --mime-type override.

- Updated README.md and SKILL.md with attachment documentation.
);
}

const encryptedBytes = new Uint8Array(await response.arrayBuffer());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

conversation/download-attachment.ts:165 Suggestion: Avoid buffering entire remote attachments in memory via arrayBuffer(). Stream response.body to disk (e.g., with pipeline) for large files, or document size limits if buffering is acceptable.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file packages/xmtp-cli/src/commands/conversation/download-attachment.ts around line 165:

Suggestion: Avoid buffering entire remote attachments in memory via `arrayBuffer()`. Stream `response.body` to disk (e.g., with `pipeline`) for large files, or document size limits if buffering is acceptable.

flags.output ??
attachment.filename ??
`${messageId.slice(0, 16)}${getExtension(mimeType)}`;
const outputPath = isAbsolute(filename) ? filename : join(cwd(), filename);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High

conversation/download-attachment.ts:137 Suggestion: Sanitize filenames from message metadata before building the output path to prevent path traversal. Extract only the basename (e.g., using path.basename) for attachment.filename, remote.filename, and decrypted.filename.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file packages/xmtp-cli/src/commands/conversation/download-attachment.ts around line 137:

Suggestion: Sanitize filenames from message metadata before building the output path to prevent path traversal. Extract only the basename (e.g., using `path.basename`) for `attachment.filename`, `remote.filename`, and `decrypted.filename`.

}),
};

const content = await readFile(args.file);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

conversation/send-attachment.ts:132 Reading the entire file into memory with readFile may cause OOM for very large files despite the docs suggesting support for large videos. Consider documenting a practical file size limit, or using streaming reads for the upload path.

🚀 Want me to fix this? Reply ex: "fix it for me".

🤖 Prompt for AI
In file packages/xmtp-cli/src/commands/conversation/send-attachment.ts around line 132:

Reading the entire file into memory with `readFile` may cause OOM for very large files despite the docs suggesting support for large videos. Consider documenting a practical file size limit, or using streaming reads for the upload path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant