Caution
This CLI is in beta status and ready for you to use. Software in this status may contain bugs or change based on feedback.
A command-line interface for Convos — agent-focused messaging built on XMTP.
- One identity per install: A single XMTP inbox shared across every conversation and DM (per ADR 011), matching the iOS app
- Multiple agents per machine: Point each agent at its own
CONVOS_HOMEto keep identities isolated - Invite system: Generate QR codes and invite links; join conversations without knowing the creator's address
- Per-conversation profiles: Display a different name/avatar in each conversation (the underlying identity stays the same)
- Explode: Notify members and remove them from a conversation
- Lock: Prevent new members from being added to a conversation
- Agent mode: Single long-running process for bots — streams messages, auto-processes joins, accepts commands via stdin
- JSON output: Every command supports
--jsonfor scripting and automation
Convos runs one XMTP inbox per install, shared across every conversation and DM — the same model as the Convos iOS app after the single-inbox refactor (ADR 011). The identity is created automatically on first use and lives at ~/.convos/identity.json.
To run multiple independent agents on one machine, point each at its own CONVOS_HOME:
CONVOS_HOME=~/.convos/alice convos conversations create --name "Alice's Group"
CONVOS_HOME=~/.convos/bob convos conversations join <slug>- Node.js >= 22
# npm
npm install -g @xmtp/convos-cli
# pnpm
pnpm add -g @xmtp/convos-cli# npx
npx @xmtp/convos-cli --help
# pnpx
pnpx @xmtp/convos-cli --help
# yarn
yarn dlx @xmtp/convos-cli --help# 1. Initialize configuration
convos init
# 2. Create a conversation (creates the install's identity on first use)
convos conversations create --name "My Group" --profile-name "Alice"
# 3. Send a message
convos conversation send-text <conversation-id> "Hello!"
# 4. Generate an invite QR code for others to join
convos conversation invite <conversation-id>
# 5. List all conversations in this install's inbox
convos conversations list
# 6. Stream messages in real-time
convos conversation stream <conversation-id>Running convos init creates ~/.convos/.env with:
| Variable | Description |
|---|---|
CONVOS_ENV |
Network: local, dev, or production |
CONVOS_API_KEY |
Agent API key — required for attachments >1MB and for profile images (auto-selects convos-api provider) |
CONVOS_UPLOAD_PROVIDER |
Upload provider override (convos-api, pinata, s3) |
Without CONVOS_API_KEY (or another CONVOS_UPLOAD_PROVIDER), conversation send-attachment fails for files over 1MB and conversation update-profile --image cannot encrypt and upload the image.
The singleton identity for this install lives at ~/.convos/identity.json. XMTP databases live at ~/.convos/db/<env>/main.db3.
The default environment is dev. Use --env to change it:
convos init --env productionConfiguration is loaded in priority order:
- CLI flags (highest)
--env-file <path>.envin current directory~/.convos/.env(default)
| Topic | Purpose |
|---|---|
agent |
Agent mode — long-running sessions with streaming I/O |
identity |
Manage this install's singleton identity |
conversations |
List, create, join, and stream conversations |
conversation |
Interact with a specific conversation |
Run convos --help for all commands, or convos <command> --help for details on a specific command. For machine-readable introspection, use convos schema.
The agent serve command runs a single long-running process that combines conversation management, message streaming, join request processing, and command handling — purpose-built for AI agents and bots.
# Create a new conversation and start serving
convos agent serve --name "My Bot" --profile-name "Assistant"
# Attach to an existing conversation
convos agent serve <conversation-id>Without agent serve, an agent has to juggle multiple processes:
convos conversation streamfor incoming messagesconvos conversations process-join-requests --watchfor new membersconvos conversation send-textfor each outgoing message (spawning a new process each time)
agent serve replaces all of that with a single process using an ndjson (newline-delimited JSON) protocol on stdin/stdout.
stdout emits one JSON object per line:
| Event | Description | Key Fields |
|---|---|---|
ready |
Session initialized | conversationId, inviteUrl, inboxId |
message |
Incoming message | id, senderInboxId, content, contentType, sentAt |
member_joined |
New member added | inboxId, conversationId |
sent |
Outgoing message confirmed | id, text or type + details |
error |
Something went wrong | message |
stdin accepts one JSON command per line:
| Command | Required Fields | Optional Fields |
|---|---|---|
send |
text |
replyTo (message ID) |
react |
messageId, emoji |
action (add/remove, default add) |
attach |
file (local path) |
mimeType, replyTo |
remote-attach |
url, contentDigest, secret, salt, nonce, contentLength |
filename, scheme |
stop |
— | — |
stderr receives the QR code and diagnostic logs (never interferes with the JSON protocol).
#!/usr/bin/env bash
# Start agent, read events, echo back every message
convos agent serve --name "Echo Bot" --profile-name "🤖 Echo" | \
while IFS= read -r event; do
type=$(echo "$event" | jq -r '.event')
case "$type" in
ready)
echo "Bot ready! Invite: $(echo "$event" | jq -r '.inviteUrl')" >&2
;;
message)
content=$(echo "$event" | jq -r '.content')
msg_id=$(echo "$event" | jq -r '.id')
# Echo the message back as a reply
echo "{\"type\":\"send\",\"text\":\"You said: $content\",\"replyTo\":\"$msg_id\"}"
;;
member_joined)
echo '{"type":"send","text":"Welcome! 👋"}'
;;
esac
done# React to a message
echo '{"type":"react","messageId":"abc123","emoji":"👍"}'
# Remove a reaction
echo '{"type":"react","messageId":"abc123","emoji":"👍","action":"remove"}'# Send a file (≤1MB sent inline, larger files auto-uploaded via provider)
echo '{"type":"attach","file":"./chart.png"}'
# Reply with an attachment
echo '{"type":"attach","file":"./report.pdf","replyTo":"abc123"}'
# Send a pre-uploaded encrypted file
echo '{"type":"remote-attach","url":"https://...","contentDigest":"...","secret":"...","salt":"...","nonce":"...","contentLength":12345}'| Flag | Description |
|---|---|
--name |
Conversation name (when creating new) |
--description |
Conversation description (when creating new) |
--permissions |
all-members or admin-only (when creating new) |
--profile-name |
Display name for this conversation |
--no-invite |
Skip generating an invite (attach mode only) |
Every install has a single XMTP identity, created automatically on first use.
# Show the install's identity (0 or 1 entries)
convos identity list
# Create the identity manually (errors if one already exists)
convos identity create --label "My Bot" --profile-name "Alice"
# View identity details (registers the XMTP client if needed)
convos identity info
# Remove the identity and wipe the XMTP database (irreversible)
convos identity remove --force# Create a conversation (uses the singleton identity, created on first use)
convos conversations create --name "Project Team" --profile-name "Alice"
# Create with admin-only permissions
convos conversations create --name "Announcement Channel" --permissions admin-only
# List all groups in this install's inbox
convos conversations list --sync
# Include DMs too
convos conversations list --include-dms
# Sync the whole inbox from the network
convos conversations syncConvos uses a serverless invite system. The creator generates a cryptographic invite slug; the joiner sends a DM join request; the creator's client processes it and adds them to the group.
# Generate an invite — displays a QR code in the terminal
convos conversation invite <conversation-id>
# Invite that expires in 1 hour
convos conversation invite <conversation-id> --expires-in 3600
# Single-use invite
convos conversation invite <conversation-id> --single-use
# JSON output (suppresses QR code)
convos conversation invite <conversation-id> --jsonJoin requests are sent as convos.org/join_request:1.0 messages containing the invite slug, joiner's profile, and memberKind: "agent" (so the creator knows a bot is joining). A plain text slug is also sent for backward compatibility with older clients.
# Join using a raw invite slug
convos conversations join <invite-slug>
# Join using a full invite URL
convos conversations join "https://dev.convos.org/v2?i=<slug>"
# Join with a display name
convos conversations join <slug> --profile-name "Bob"
# Send join request without waiting for acceptance
convos conversations join <slug> --no-wait
# Wait up to 2 minutes
convos conversations join <slug> --timeout 120The creator's client must be running to process incoming join requests:
# Process all pending join requests
convos conversations process-join-requests
# Continuously watch for join requests
convos conversations process-join-requests --watch
# Process for a specific conversation only
convos conversations process-join-requests --conversation <id># Send different message types
convos conversation send-text <id> "Hello!"
convos conversation send-reaction <id> <message-id> add "👍"
convos conversation send-reply <id> <message-id> "I agree!"
# Read messages
convos conversation messages <id> --sync --limit 10
# Stream messages in real-time
convos conversation stream <id>
convos conversation stream <id> --timeout 60# Send a photo (small files ≤1MB sent inline)
convos conversation send-attachment <id> ./photo.jpg
# Large files are automatically encrypted and uploaded via configured provider
convos conversation send-attachment <id> ./video.mp4
# Force remote upload even for small files
convos conversation send-attachment <id> ./photo.jpg --remote
# Override MIME type
convos conversation send-attachment <id> ./file.bin --mime-type image/png
# Per-command upload provider (no .env needed)
convos conversation send-attachment <id> ./photo.jpg \
--upload-provider pinata --upload-provider-token <jwt>
# Encrypt only (for manual upload workflows)
convos conversation send-attachment <id> ./photo.jpg --encrypt
# Send a pre-uploaded encrypted file
convos conversation send-remote-attachment <id> <url> \
--content-digest <hex> --secret <base64> --salt <base64> \
--nonce <base64> --content-length <bytes>
# Download an attachment (handles both inline and remote transparently)
convos conversation download-attachment <id> <message-id>
# Download to a specific path
convos conversation download-attachment <id> <message-id> --output ./photo.jpg
# Reply with a photo
convos conversation send-reply <id> <message-id> --file ./photo.jpgTo enable uploads for large files, set your agent API key in ~/.convos/.env:
CONVOS_API_KEY=<your-agent-api-key>This auto-selects the convos-api provider. Other providers (pinata, s3) are also available via CONVOS_UPLOAD_PROVIDER.
Profiles are per-conversation — the same underlying identity can present a different display name and avatar in each conversation.
Profiles are sent as ProfileUpdate messages to the group. The CLI no longer writes profiles to appData (this was removed to fix a data corruption bug). When reading profiles, message-sourced profiles take precedence, with appData as a read-only fallback for profiles written by older clients.
When new members are added (via invite or directly), a ProfileSnapshot message is sent containing all current member profiles so the new joiner has everyone's data immediately.
# Set your display name in a conversation
convos conversation update-profile <id> --name "Alice"
# Set name and avatar
convos conversation update-profile <id> --name "Alice" --image "https://example.com/avatar.jpg"
# Go anonymous (clear profile)
convos conversation update-profile <id> --name "" --image ""
# View all member profiles
convos conversation profiles <id>
convos conversation profiles <id> --json# View members
convos conversation members <id>
# Add/remove members
convos conversation add-members <id> <inbox-id>
convos conversation remove-members <id> <inbox-id>
# Update metadata
convos conversation update-name <id> "New Name"
convos conversation update-description <id> "New description"
# View permissions
convos conversation permissions <id>Prevent new members from being added:
convos conversation lock <id>
convos conversation lock <id> --unlockNotify members and remove them from the MLS group:
# Explode immediately
convos conversation explode <id> --force
# Schedule explosion for a future date
convos conversation explode <id> --scheduled "2025-03-01T00:00:00Z"Exploding sends an ExplodeSettings notification to all members (so iOS and other clients trigger their cleanup flow), updates group metadata with the expiration timestamp, and removes every other member from the group. Receiving clients drop the conversation locally on whichever arrives first: the ExplodeSettings message or the MLS remove commit.
The install's identity is not destroyed — matching the iOS model (ADR 011 §5 / ADR 004 C9), one identity spans every conversation on this install.
When using --scheduled, members are notified but not removed — clients handle cleanup when the time arrives.
All commands support --json for machine-readable output:
# Create a conversation and capture the ID
CONV_ID=$(convos conversations create --name "Test" --json | jq -r '.conversationId')
# Send a message
convos conversation send-text "$CONV_ID" "Hello!"
# Read messages as JSON
convos conversation messages "$CONV_ID" --sync --json
# Generate invite and capture the URL
INVITE_URL=$(convos conversation invite "$CONV_ID" --json | jq -r '.url')Use --fields to limit JSON output to specific fields (implicitly enables --json). Supports dot notation for nested paths:
# Only get message id, content, and sender
convos conversation messages <id> --fields id,content,senderInboxId
# Nested field extraction
convos conversation messages <id> --fields id,content,contentType.typeId,sentAt
# Works on any command
convos conversation profiles <id> --fields profiles
convos conversations list --fields conversationId,nameThis is especially useful for AI agents to conserve context window tokens by fetching only the fields they need.
Use convos schema to discover tool capabilities at runtime as machine-readable JSON — no docs or skill files needed:
# List all commands with summaries
convos schema
# Full schema for a specific command (args, flags, examples)
convos schema conversation send-text
# Filter by topic
convos schema --topic conversation
convos schema --topic agentCommon flags (--json, --fields, --env, etc.) are listed once at the top level rather than repeated on every command.
Use --verbose to see detailed client initialization info. When combined with --json, verbose logs go to stderr:
convos identity info --verbose~/.convos/
├── .env # Global config (env only)
├── identity.json # Singleton identity: wallet key, db key, inbox ID
└── db/
└── dev/ # XMTP database for the identity, by environment
└── main.db3
To run multiple agents on one host, give each its own directory via --home or CONVOS_HOME.
┌──────────────────────────────────────────┐
│ @xmtp/convos-cli │
│ │
│ Commands: │
│ agent serve (long-running bot mode) │
│ identity create/list/info/remove │
│ conversations create/join/list/sync │
│ conversation invite/explode/lock │
│ conversation send-text/stream/... │
│ conversation send-attachment/download │
│ conversation update-profile/profiles │
│ │
│ ┌────────────────────────────────────┐ │
│ │ @xmtp/node-sdk │ │
│ │ │ │
│ │ One XMTP client per install │ │
│ │ Group/DM management │ │
│ │ Message encryption & delivery │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
@xmtp/convos-cli exports its core functionality for use in other applications:
import {
createIdentityStore,
getClient,
getIdentityAndClient,
createInviteSlug,
parseInvite,
verifyInvite,
parseAppData,
serializeAppData,
upsertProfile,
// Profile messages (primary profile source)
encodeProfileUpdate,
decodeProfileUpdate,
encodeProfileSnapshot,
decodeProfileSnapshot,
sendProfileUpdate,
sendProfileSnapshot,
resolveProfilesFromMessages,
// Join request content type
JoinRequestCodec,
ContentTypeJoinRequest,
ConvosBaseCommand,
} from "@xmtp/convos-cli";See the exports for the full API.
This package includes an agent skill (skills/convos-cli/SKILL.md) that teaches AI coding agents how to use the Convos CLI.
Claude Code
Add the skill directory to your project's .claude/settings.json:
{
"skills": ["./node_modules/@xmtp/convos-cli/skills"]
}Other agents (Cursor, Windsurf, Codex, etc.)
Use openskills to install the skill:
npx openskills install ./node_modules/@xmtp/convos-cli/skillsOr point your agent to node_modules/@xmtp/convos-cli/skills/convos-cli/SKILL.md directly.
# Run all tests (fast, no network required)
npm test
# Watch mode
npm run test:watch# Install dependencies
npm install
# Build TypeScript
npm run build
# Dev mode (tsx, no build needed)
npm run dev -- conversations list
# Run built version
npm start -- conversations list
# Type check without emitting
npm run typecheck