convos-cli

star 13

Use when working with Convos messaging - privacy-focused ephemeral messaging with per-conversation identities, invites, profiles, and group management via the convos CLI tool

xmtplabs By xmtplabs schedule Updated 4/23/2026

name: convos-cli description: Use when working with Convos messaging - privacy-focused ephemeral messaging with per-conversation identities, invites, profiles, and group management via the convos CLI tool

Convos CLI

The Convos CLI (convos) is a command-line tool for privacy-focused ephemeral messaging built on XMTP. Unlike standard XMTP, Convos creates a unique identity per conversation so conversations cannot be linked or correlated.

Key differences from standard XMTP:

  • Per-conversation identities: Each conversation gets its own wallet, inbox, and database
  • No global wallet key: Identities are created automatically when creating/joining conversations
  • Invite system: Serverless QR code + URL invites for joining conversations
  • Per-conversation profiles: Different display name and avatar in each conversation
  • Explode: Permanently destroy a conversation and all cryptographic keys
  • Lock: Prevent new members from being added

Prerequisites

Initialize Configuration

# generate config and save to default path (~/.convos/.env)
convos init

# output config to console instead of writing to file
convos init --stdout

# initialize for production environment
convos init --env production

# overwrite existing config
convos init --force

# initialize with a custom data directory (useful for multiple agents)
convos init --home /path/to/agent1-data

# or use the CONVOS_HOME environment variable
CONVOS_HOME=/path/to/agent1-data convos init

This creates a .env file with:

  • CONVOS_ENV - Network environment (local, dev, production)
  • CONVOS_API_KEY - Agent API key for uploads (auto-selects convos-api provider)
  • CONVOS_UPLOAD_PROVIDER - Upload provider override (convos-api, pinata, s3)

Note: Unlike standard XMTP, there is no global wallet key. Each conversation creates its own identity stored in ~/.convos/identities/.

Custom Data Directory

By default, all data is stored in ~/.convos/. To use a different directory (e.g., when running multiple agents on one machine), use the --home flag or the CONVOS_HOME environment variable:

# via flag (works on any command)
convos conversations list --home /path/to/agent-data

# via environment variable
export CONVOS_HOME=/path/to/agent-data
convos conversations list

Priority: --home flag > CONVOS_HOME env var > ~/.convos

Configuration Loading Priority

  1. CLI flags (highest priority)
  2. Explicit --env-file <path>
  3. .env in the current working directory
  4. <convos-home>/.env (default: ~/.convos/.env)

Command Structure

convos [TOPIC] [COMMAND] [ARGUMENTS] [FLAGS]

Topics

Topic Purpose
agent Agent mode — long-running sessions with streaming I/O
identity Manage per-conversation identities (inboxes)
conversations List, create, join, and stream conversations
conversation Interact with a specific conversation

Standalone Commands

Command Purpose
init Initialize configuration and directory structure
reset Delete all identities and conversation data (preserves .env)
schema Introspect CLI commands as machine-readable JSON (args, flags, examples)

Output Modes

All commands support --json for machine-readable JSON output:

convos conversations list --json

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,name

Use --verbose to see detailed client initialization logs. When combined with --json, verbose output goes to stderr:

convos identity info <id> --verbose
convos conversations list --json --verbose 2>/dev/null

Common Workflows

Create a Conversation

# create a conversation (auto-creates a per-conversation identity)
convos conversations create --name "My Group" --profile-name "Alice"

# create with admin-only permissions
convos conversations create --name "Announcement Channel" --permissions admin-only

# create and capture the conversation ID
CONV_ID=$(convos conversations create --name "Test" --json | jq -r '.conversationId')

Send Messages

# send a text message
convos conversation send-text <conversation-id> "Hello, world!"

# send a reaction
convos conversation send-reaction <conversation-id> <message-id> add "👍"
# remove a reaction
convos conversation send-reaction <conversation-id> <message-id> remove "👍"

# send a reply referencing another message
convos conversation send-reply <conversation-id> <message-id> "Replying to you"

# reply with a photo
convos conversation send-reply <conversation-id> <message-id> --file ./photo.jpg

# reply with a large file (auto-uploaded via provider)
convos conversation send-reply <conversation-id> <message-id> --file ./video.mp4

# send a read receipt (silent — no visible message, no push notification)
convos conversation send-read-receipt <conversation-id>

# query last read times per member (nanosecond timestamps)
convos conversation last-read-times <conversation-id>
convos conversation last-read-times <conversation-id> --sync --json

# send a typing indicator (silent — notifies others you are typing)
convos conversation send-typing-indicator <conversation-id>

# stop typing indicator
convos conversation send-typing-indicator <conversation-id> --stop

Send Attachments

# send a photo (small files ≤1MB sent inline, large files auto-uploaded via provider)
convos conversation send-attachment <conversation-id> ./photo.jpg

# force remote upload even for small files
convos conversation send-attachment <conversation-id> ./photo.jpg --remote

# override MIME type
convos conversation send-attachment <conversation-id> ./file.bin --mime-type image/png

# use upload provider via flags (no .env needed)
convos conversation send-attachment <conversation-id> ./photo.jpg \
  --upload-provider pinata --upload-provider-token <jwt>

# encrypt only — outputs encrypted file + decryption keys for manual upload
convos conversation send-attachment <conversation-id> ./photo.jpg --encrypt

# send a pre-uploaded encrypted file with decryption keys
convos conversation send-remote-attachment <conversation-id> <url> \
  --content-digest <hex> --secret <base64> --salt <base64> \
  --nonce <base64> --content-length <bytes> --filename photo.jpg

# download an attachment (handles both inline and remote transparently)
convos conversation download-attachment <conversation-id> <message-id>

# download to a specific path
convos conversation download-attachment <conversation-id> <message-id> --output ./photo.jpg

# save encrypted payload without decrypting
convos conversation download-attachment <conversation-id> <message-id> --raw

To enable automatic upload for large files, set your agent API key in .env:

CONVOS_API_KEY=<your-agent-api-key>

This auto-selects the convos-api upload provider. Other providers (pinata, s3) are also available via CONVOS_UPLOAD_PROVIDER.

Read Messages

# list messages (default: descending order)
convos conversation messages <conversation-id>
# sync from network and limit results
convos conversation messages <conversation-id> --sync --limit 10

Stream Messages in Real-Time

# stream messages from a single conversation
convos conversation stream <conversation-id>
# stop after 60 seconds
convos conversation stream <conversation-id> --timeout 60

List Conversations

# list all conversations across all identities
convos conversations list
# sync from network before listing
convos conversations list --sync

Invite System

Convos uses a serverless invite system. The creator generates a cryptographic invite URL; the person joining must open the URL in the Convos app (or scan the QR code); then the creator processes the join request to add them to the group.

Important: Adding someone to a conversation is a multi-step process:

  1. Generate an invite (creator side) — produces a URL and QR code
  2. Person opens the invite URL in Convos or scans the QR code — this sends a join request to the creator via DM
  3. Creator processes the join request — this validates the request and adds the person to the group

The creator must process join requests after the person has opened/scanned the invite. If you don't know when that will happen, use --watch with a timeout to stream and process requests as they arrive.

Inspect an Invite

# decode and inspect an invite without joining (useful for debugging)
convos conversations inspect-invite <invite-slug>

# inspect a full invite URL
convos conversations inspect-invite "https://dev.convos.org/v2?i=<slug>"

# output as JSON
convos conversations inspect-invite <slug> --json

This displays the invite's tag, creator inbox ID, conversation name, expiration dates, signature validity, and whether the invite is expired — without creating any identities or sending join requests.

Create an Invite

# generate invite — displays QR code in terminal
convos conversation invite <conversation-id>

# generate invite with 1-hour expiry
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> --json

# capture invite URL for scripting
INVITE_URL=$(convos conversation invite <conversation-id> --json | jq -r '.url')

Person Joins via Invite

The person being invited must open the invite URL in the Convos app or scan the QR code with Convos. This can be done:

  • On iOS: Open the URL in Safari (redirects to Convos app) or scan the QR code from within the app
  • Via CLI: Use convos conversations join
# 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"

# join with a display name and avatar image
convos conversations join <slug> --profile-name "Bot" --profile-image "https://example.com/avatar.jpg"

# join with custom metadata
convos conversations join <slug> --metadata role=assistant --metadata version=2

# send join request without waiting for acceptance
convos conversations join <slug> --no-wait

# wait up to 2 minutes for acceptance
convos conversations join <slug> --timeout 120

Process Join Requests (Creator Side)

After the person has opened/scanned the invite, the creator must process the join request:

# process all pending join requests (use when you know the invite has already been opened)
convos conversations process-join-requests

# process for a specific conversation only
convos conversations process-join-requests --conversation <id>

# watch for join requests with a timeout (use when you don't know when the invite will be opened)
convos conversations process-join-requests --watch --conversation <id>
# note: use ctrl-c or a timeout to stop watching

# continuously watch for all join requests (keep running in background)
convos conversations process-join-requests --watch

Per-Conversation Profiles

Each conversation has independent profiles — you can have a different name and avatar in each.

Profile updates 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 where concurrent read-modify-write cycles could erase invite tags and other members' profiles). When reading profiles, message-sourced profiles take precedence, with appData as a read-only fallback for profiles written by older clients (e.g., iOS).

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 — solving the MLS forward secrecy problem where older messages may be undecryptable.

Profile resolution precedence:

  1. Latest ProfileUpdate from that member — highest priority, most recent self-authored update
  2. Most recent ProfileSnapshot containing that member — fallback when no ProfileUpdate exists
  3. appData profiles — legacy fallback for backward compatibility with older clients
  4. No profile — member has no name/avatar set

Both ProfileUpdate and ProfileSnapshot are silent messages (shouldPush = false) — they do not appear in chat or trigger notifications.

# set display name
convos conversation update-profile <conversation-id> --name "Alice"

# set name and avatar
convos conversation update-profile <conversation-id> --name "Alice" --image "https://example.com/avatar.jpg"

# go anonymous (clear profile)
convos conversation update-profile <conversation-id> --name "" --image ""

# view all member profiles
convos conversation profiles <conversation-id>
convos conversation profiles <conversation-id> --json

Identity Management

Identities are created automatically when creating/joining conversations, but you can manage them directly.

# list all identities
convos identity list

# create an identity manually
convos identity create --label "Work Chat" --profile-name "Alice"

# view identity details (connects to XMTP to show inbox ID)
convos identity info <identity-id>

# remove an identity (destroys all keys — irreversible)
convos identity remove <identity-id> --force

Reset All Data

Delete all identities and conversation data. The .env configuration is preserved.

# reset with confirmation prompt
convos reset

# reset without confirmation
convos reset --force

Group Management

# view members
convos conversation members <conversation-id>

# add members by inbox ID
convos conversation add-members <conversation-id> <inbox-id>

# remove members
convos conversation remove-members <conversation-id> <inbox-id>

# update group name
convos conversation update-name <conversation-id> "New Name"

# update group description
convos conversation update-description <conversation-id> "New description"

# view permissions
convos conversation permissions <conversation-id>

Lock a Conversation

Prevent new members from joining by setting the addMember permission to deny. This also invalidates all existing invites. Only super admins can lock/unlock.

# lock
convos conversation lock <conversation-id>

# unlock (previously shared invites remain invalid — generate new ones)
convos conversation lock <conversation-id> --unlock

Explode a Conversation

Permanently destroy a conversation and all its cryptographic keys. Sends an ExplodeSettings notification to all members (so iOS and other clients can trigger their cleanup), updates group metadata with the expiration timestamp, removes all members, then destroys the local identity. Irreversible.

# explode immediately
convos conversation explode <conversation-id> --force

# schedule explosion for a future date (ISO8601)
convos conversation explode <conversation-id> --scheduled "2025-03-01T00:00:00Z"

When scheduled, the ExplodeSettings message is sent with a future expiresAt date. Members are notified but not removed — clients handle cleanup when the time arrives. When immediate (no --scheduled), members are removed and the local identity is destroyed right away.

Assistant Attestation

Cryptographically verify that an agent was provisioned by the Convos backend. Attestations use Ed25519 signatures over sha256(inboxId || timestamp).

# generate a test attestation (creates a key pair, signs, outputs JWKS)
convos attestation generate <inbox-id>
convos attestation generate <inbox-id> --kid my-key-2026 --json

# verify an attestation against a JWKS endpoint
convos attestation verify <inbox-id> \
  --attestation <base64url-sig> \
  --attestation-ts <iso8601> \
  --attestation-kid <kid>

# verify against a raw public key
convos attestation verify <inbox-id> \
  --attestation <sig> \
  --attestation-ts <ts> \
  --public-key <base64url-pubkey>

# verify against a local JWKS file
convos attestation verify <inbox-id> \
  --attestation <sig> \
  --attestation-ts <ts> \
  --attestation-kid <kid> \
  --jwks-file ./agents.json

Agents include attestation in their profile metadata when joining:

# agent serve with attestation (typically provided by the backend)
convos agent serve --name "Bot" \
  --attestation <sig> \
  --attestation-ts <ts> \
  --attestation-kid <kid>

# or via environment variables
CONVOS_ATTESTATION=<sig> CONVOS_ATTESTATION_TS=<ts> CONVOS_ATTESTATION_KID=<kid> \
  convos agent serve --name "Bot"

# join with attestation
convos conversations join <slug> \
  --attestation <sig> \
  --attestation-ts <ts> \
  --attestation-kid <kid>

Sync Data from Network

# sync conversation list
convos conversations sync

# sync a single conversation
convos conversation sync <conversation-id>

Agent Mode

The agent serve command runs a long-running process that combines conversation creation, message streaming, join request processing, and stdin command handling — ideal for AI agents and bots.

Quick Start (Agent)

# 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>

# create with admin-only permissions
convos agent serve --name "Agent" --permissions admin-only

Protocol

The agent uses an ndjson (newline-delimited JSON) protocol:

  • stdout: Events (one JSON object per line)
  • stdin: Commands (one JSON object per line)
  • stderr: QR code + diagnostic logs

Events (stdout)

Event Description Key Fields
ready Session started conversationId, inviteUrl, inboxId
message New message received id, senderInboxId, senderProfile (optional: name, image), content, contentType, sentAt, catchup (optional)
typing Member typing status changed senderInboxId, isTyping, conversationId
member_joined Member joined via invite inboxId, conversationId, catchup (optional)
sent Message sent confirmation id, text, replyTo (optional), type (optional)
heartbeat Periodic health check conversationId, activeStreams
error Error occurred message

Messages with catchup: true were fetched during stream reconnection (missed while disconnected).

Commands (stdin)

{"type":"send","text":"Hello, world!"}
{"type":"send","text":"Replying to you","replyTo":"<message-id>"}
{"type":"react","messageId":"<message-id>","emoji":"👍"}
{"type":"react","messageId":"<message-id>","emoji":"👍","action":"remove"}
{"type":"attach","file":"./photo.jpg"}
{"type":"attach","file":"./photo.jpg","replyTo":"<message-id>"}
{"type":"attach","file":"./photo.jpg","mimeType":"image/jpeg"}
{"type":"remote-attach","url":"https://...","contentDigest":"<hex>","secret":"<base64>","salt":"<base64>","nonce":"<base64>","contentLength":12345,"filename":"photo.jpg"}
{"type":"rename","name":"New Group Name"}
{"type":"read-receipt"}
{"type":"typing"}
{"type":"typing","isTyping":false}
{"type":"lock"}
{"type":"unlock"}
{"type":"explode"}
{"type":"explode","scheduled":"2025-03-01T00:00:00Z"}
{"type":"stop"}
Command Required Fields Optional Fields
send text replyTo
react messageId, emoji action (add/remove, default: add)
attach file (local path) mimeType, replyTo
remote-attach url, contentDigest, secret, salt, nonce, contentLength filename, scheme
rename name
read-receipt
typing isTyping (bool, default: true)
lock
unlock
explode scheduled (ISO8601 date)
stop

Small attachments (≤1MB) are sent inline. Larger files are auto-encrypted and uploaded via the configured upload provider (e.g., Pinata).

Lock prevents new members from joining by rotating the invite tag and setting addMember permission to deny. Unlock reverses this (previously shared invites remain invalid). Explode permanently destroys the conversation — sends ExplodeSettings notification, removes members, and deletes the local identity. Immediate explode triggers agent shutdown. Rename updates the conversation name visible to all members.

How It Works

When started, agent serve:

  1. Creates or attaches to a conversation
  2. Displays QR code invite on stderr (so users can scan and join)
  3. Emits ready event with conversation ID, invite URL, and identity info
  4. Processes pending join requests from before the agent started
  5. Streams messages — emits message events as they arrive in real-time
  6. Streams DM join requests — automatically adds new members and emits member_joined
  7. Reads stdin — accepts send, rename, lock, unlock, explode, and stop commands
  8. Emits heartbeat (optional) — periodic health check events when --heartbeat is set
  9. Catches up on reconnect — if a stream disconnects and reconnects, fetches any missed messages since the last seen timestamp

All of these run concurrently. The agent stays alive until SIGINT, SIGTERM, stdin close, a stop command, or an immediate explode.

Example: Agent Integration

# Start the agent, pipe commands in, read events out
convos agent serve --name "Bot" --profile-name "AI Assistant" | while IFS= read -r event; do
  type=$(echo "$event" | jq -r '.event')
  case "$type" in
    ready)
      echo "Bot ready! Invite URL: $(echo "$event" | jq -r '.inviteUrl')" >&2
      ;;
    message)
      content=$(echo "$event" | jq -r '.content')
      echo "Received: $content" >&2
      # Send a reply (write JSON command to agent's stdin)
      msg_id=$(echo "$event" | jq -r '.id')
      echo "{\"type\":\"send\",\"text\":\"You said: $content\",\"replyTo\":\"$msg_id\"}"
      ;;
    member_joined)
      inbox=$(echo "$event" | jq -r '.inboxId')
      echo "New member: $inbox" >&2
      echo "{\"type\":\"send\",\"text\":\"Welcome!\"}"
      ;;
  esac
done

Agent Flags

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
--identity Use an existing unlinked identity
--label Local label for the identity
--no-invite Skip generating an invite (attach mode)
--heartbeat Emit heartbeat events every N seconds (0 to disable, default: 0)

Important Concepts

Per-Conversation Identities

Every conversation has its own:

  • Wallet key (secp256k1 private key)
  • DB encryption key (32-byte key)
  • XMTP inbox (unique inbox ID)
  • Local database (SQLite)

Identities are stored in <convos-home>/identities/<id>.json. Databases are stored in <convos-home>/db/<env>/<id>.db3. The data directory defaults to ~/.convos/ but can be overridden with --home or CONVOS_HOME.

Invite Flow

  1. Creator generates an invite URL/QR code (contains encrypted conversation token + creator's inbox ID)
  2. Person opens the invite URL in Convos app (or scans the QR code) — this creates a per-conversation identity and sends a DM join request to the creator
  3. Creator processes the join request — validates the invite signature, decrypts the conversation token, and adds the person to the group
  4. Person is now a member with their own isolated identity

Key point: Step 3 must happen after step 2. The creator must either run process-join-requests after the invite has been opened, or use --watch to stream and process requests as they arrive.

Profile Messages

Member profiles are stored as XMTP group messages using two custom content types:

  • ProfileUpdate (convos.org/profile_update:1.0) — sent by a member when they change their own name or avatar. The sender's inbox ID is implicit from the XMTP message, preventing spoofing.
  • ProfileSnapshot (convos.org/profile_snapshot:1.0) — sent after adding members to a group. Contains all current member profiles so new joiners have data immediately (solves MLS forward secrecy gap).

Both are silent (no push notification, not displayed in chat). The CLI reads appData profiles as a fallback for backward compatibility with older clients, but does not write profiles there. Custom XMTP content codecs (ProfileUpdateCodec, ProfileSnapshotCodec) are registered with the XMTP client at creation time so the SDK can decode these message types natively.

Profiles support typed metadata — arbitrary key-value pairs where values can be string, number (double), or boolean. Metadata is carried in both ProfileUpdate and ProfileSnapshot messages via a map<string, MetadataValue> protobuf field. Use --metadata key=value on update-profile (repeatable, auto-typed: "true"/"false" → bool, numeric → number, else string). Metadata merges with existing values (new keys overwrite, unmentioned keys preserved).

Profile images are encrypted end-to-end using the same scheme as iOS: HKDF-SHA256 derives a per-image AES-256-GCM key from the group's imageEncryptionKey (stored in appData) + random 32-byte salt, then encrypts with a random 12-byte nonce. The encrypted blob is uploaded via the configured upload provider and the URL + salt + nonce are sent as EncryptedProfileImageRef in the ProfileUpdate message. If no imageEncryptionKey exists for the group, the CLI generates one and writes it to appData.

Supported upload providers:

  • Convos API: CONVOS_UPLOAD_PROVIDER=convos-api, CONVOS_API_KEY=<agent-assets-api-key>, optional CONVOS_API_BASE_URL=<url> (auto-derived from XMTP env: dev → https://api.dev.convos.xyz/api, production → https://api.prod.convos.xyz/api). Uses the agent asset upload endpoint (GET /v2/agents/assets/presigned) with X-Agent-API-Key header auth — no JWT step needed.
  • Pinata (IPFS): CONVOS_UPLOAD_PROVIDER=pinata, CONVOS_UPLOAD_PROVIDER_TOKEN=<jwt>, optional CONVOS_UPLOAD_PROVIDER_GATEWAY=<url>
  • S3 (direct): CONVOS_UPLOAD_PROVIDER=s3, CONVOS_UPLOAD_PROVIDER_TOKEN=<accessKeyId>:<secretAccessKey>, CONVOS_S3_BUCKET=<bucket>, optional CONVOS_S3_REGION=<region> (default: us-east-1), optional CONVOS_S3_ENDPOINT=<url> (for S3-compatible services like MinIO, R2), optional CONVOS_UPLOAD_PROVIDER_GATEWAY=<public-url-prefix>

Join Request Messages

Join requests use a structured content type instead of plain text:

  • JoinRequest (convos.org/join_request:1.0) — sent as a DM to the conversation creator when joining via invite. Contains the invite slug, joiner's profile (name, image, memberKind), and optional metadata.

The CLI sets memberKind: "agent" by default on all join requests so the creator knows a bot is joining. For backward compatibility, the CLI sends both the JoinRequestContent message and a plain text slug — older clients that don't understand the new content type will read the text fallback. When processing incoming join requests, the CLI tries JoinRequestContent first, then falls back to plain text.

Consent States

State Meaning
allowed Messages are welcome
denied Messages are blocked
unknown No decision made

Environment Networks

Network Use Case
local Local XMTP node
dev Development/testing (default)
production Production use

Data Directory

The data directory defaults to ~/.convos/ but can be overridden with --home or CONVOS_HOME:

<convos-home>/              # default: ~/.convos/
├── .env                    # Global config (env only)
├── identities/
│   ├── <id-1>.json         # Identity: wallet key, db key, conversation link
│   └── <id-2>.json
└── db/
    └── dev/                # XMTP databases by environment
        ├── <id-1>.db3
        └── <id-2>.db3

Error Handling

  1. Not initialized: Run convos init to create configuration
  2. No identities: Create a conversation or identity first
  3. Identity not found: Use convos identity list to see available identities
  4. Conversation not found: Sync first with convos conversations sync
  5. Permission denied: Check group permissions with convos conversation permissions
  6. Invite expired or invalid: Generate a new invite with convos conversation invite

Complete Example

# 1. initialize (first time only)
convos init --env dev

# 2. create a conversation
CONV=$(convos conversations create --name "Project Team" --profile-name "Alice" --json)
CONV_ID=$(echo "$CONV" | jq -r '.conversationId')

# 3. generate an invite for others to join
convos conversation invite "$CONV_ID"

# 4. wait for the person to open the invite URL or scan the QR code,
#    then process their join request
convos conversations process-join-requests --conversation "$CONV_ID"

# OR: if you don't know when they'll open it, watch for requests
# convos conversations process-join-requests --watch --conversation "$CONV_ID"

# 5. send a message
convos conversation send-text "$CONV_ID" "Welcome to the team!"

# 6. stream messages
convos conversation stream "$CONV_ID" --timeout 300

Tips

  1. Always display the full QR code: The conversation invite and conversations create commands output a scannable QR code rendered in Unicode block characters followed by the invite URL. When showing the user the result, you must display the complete, unmodified command output so the QR code renders correctly in the terminal. Do not summarize, truncate, or omit the QR code — it is the primary way users share invites. Always show the full stdout output to the user. When running agent serve, the QR code is saved as a PNG file (path in the qrCodePath field of the ready event) — display it to the user using the read tool so they can scan it.
  2. Never use markdown in messages: Convos does not render markdown. When sending messages (via send-text, send-reply, or agent send commands), always use plain text. Do not use markdown formatting like **bold**, *italic*, # headings, `code`, [links](url), or bullet lists with - or * . Write naturally in plain text instead.
  3. Identities are automatic: You rarely need to manage them directly — creating/joining conversations handles it
  4. Use JSON output for scripting: Add --json flag when extracting data programmatically
  5. Use --fields to limit output: When fetching messages or other large responses, use --fields to include only the fields you need — this saves context window tokens and reduces noise. e.g. --fields id,content,senderInboxId
  6. Sync before reading: Add --sync flag when reading messages to ensure fresh data
  7. Process join requests after invite is opened: After generating an invite, wait for the person to open/scan it, then run process-join-requests. If you don't know when they'll open it, use --watch to stream requests as they arrive
  8. Lock before exploding: Lock a conversation first to prevent new joins, then explode when ready
  9. Dangerous operations require --force: Commands like explode, identity remove, and lock prompt for confirmation unless --force is passed
  10. Check command help: Run convos <command> --help for full flag documentation
  11. Use convos schema for runtime introspection: convos schema lists all commands as JSON, convos schema <command> shows full args/flags/examples for a specific command. Useful for discovering capabilities without pre-loaded docs
Install via CLI
npx skills add https://github.com/xmtplabs/convos-ios --skill convos-cli
Repository Details
star Stars 13
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator