Skill: amzzzzzzz/imessage-voice-memo-skill (version 0.1.2)

--- name: voice-memo description: "Send native iMessage voice bubbles with ElevenLabs TTS via BlueBubbles. Use when: user asks to send a voice message, wants something spoken aloud, storytelling or summaries requested, or voice delivery would be more engaging than text. Requires ElevenLabs API key and BlueBubbles." homepage: https://github.com/amzzzzzzz/imessage-voice-memo-skill metadata: { "openclaw": { "emoji": "🎙️", "platform": "macOS", "requires": { "bins": ["curl", "afconvert"] }, "credentials": ["BLUEBUBBLES_PASSWORD", "ELEVENLABS_API_KEY"] } } --- # Voice Memo Send native iMessage voice bubbles (not file attachments) using ElevenLabs TTS and BlueBubbles. ## Quick Start Run the script with text and recipient: ```bash scripts/send-voice-memo.sh "Your message here" +14169060839 ``` This will: 1. Generate TTS audio via ElevenLabs (Rachel voice by default) 2. Convert to Opus CAF @ 24kHz (iMessage native format) 3. Send as native voice bubble via BlueBubbles ## Requirements - BlueBubbles running locally with Private API enabled - ElevenLabs API key (for TTS) - macOS (for `afconvert` audio conversion) - Environment variables in `~/.openclaw/.env`: ```bash [REDACTED_SECRET] BLUEBUBBLES_PASSWORD=your-password-here # Optional overrides: ELEVENLABS_VOICE_ID=21m00Tcm4TlvDq8ikWAM # Rachel (default) ELEVENLABS_MODEL_ID=eleven_turbo_v2_5 # Turbo v2.5 (default) ``` ## The Working Formula **Critical parameters discovered 2026-02-19:** | Parameter | Value | Why | |-----------|-------|-----| | chatGuid | `any;-;+PHONE` | NOT `iMessage;-;` (causes timeouts) | | method | `private-api` | Required for native bubble | | isAudioMessage | `true` | Required | | Audio format | Opus @ 24kHz in CAF | iMessage native format | | Pre-convert | Yes | Don't let BlueBubbles convert (wrong codec) | ## Voice Options **Default voice:** Rachel (ElevenLabs) - Voice ID: `21m00Tcm4TlvDq8ikWAM` - Model: `eleven_turbo_v2_5` (fast, natural) - Cost: ~$0.04 per 30s message **Expressive tags:** - `[laughs]` — natural laughter - `[sighs]` — expressive sigh - `[excited]` — energetic delivery Example: `"[excited] Oh my god, it worked!"` For full voice list and IDs, see [VOICES.md](references/VOICES.md). ## Bidirectional Voice Memos **Sending (Amz → Amy):** Use this skill. Native voice bubbles appear with waveform UI. **Receiving (Amy → Amz):** BlueBubbles auto-converts incoming voice memos to MP3. OpenClaw transcribes via Whisper. Transcribed text flows into conversation context automatically. **Memory note:** Incoming voice memo transcriptions flow into conversation context like any text message. They are NOT automatically persisted to memory or files — the agent must explicitly choose to store them, same as any conversation content. If you want to prevent transcriptions from being retained, instruct the agent not to record voice memo content in memory. ## Troubleshooting **Voice bubble arrives as file attachment:** - Check `method=private-api` is set - Verify chatGuid uses `any;-;` prefix (not `iMessage;-;`) - Check response has `"isAudioMessage": true` **API times out:** - Use `any;-;+PHONE` format for chatGuid - Verify BlueBubbles Private API is enabled - Restart BlueBubbles if consistently slow **Audio is 0 seconds / unplayable:** - Ensure pre-conversion to Opus @ 24kHz - Don't let BlueBubbles convert (uses wrong codec) - Verify with: `afinfo output.caf` (should show opus @ 24000 Hz)

path: README.md
content: # iMessage Voice Memo Skill for OpenClaw

Send **native iMessage voice bubbles** (not file attachments) via BlueBubbles.

![Voice Bubble Demo](https://img.shields.io/badge/Status-Working-brightgreen)
![Platform](https://img.shields.io/badge/Platform-macOS-blue)
![License](https://img.shields.io/badge/License-MIT-yellow)

## Features

- 🎤 **Native voice bubbles** — Appears with waveform, tap to play (not as file attachment)
- 🗣️ **ElevenLabs TTS** — Natural-sounding voice synthesis
- ⚡ **Fast** — End-to-end in ~0.4 seconds
- 🔄 **Bidirectional** — Send and receive voice memos via iMessage

## Requirements

- **macOS** (for `afconvert`)
- **BlueBubbles Server** running locally with **Private API enabled**
- **ElevenLabs API key** (for TTS)

## Installation

1. Clone this repo:
```bash
git clone https://github.com/amzzzzzzz/imessage-voice-memo-skill.git
cd imessage-voice-memo-skill
```

2. Copy the skill to your OpenClaw workspace:
```bash
cp -r . ~/.openclaw/workspace/skills/voice-memo-imessage/
```

3. Add required environment variables to `~/.openclaw/.env`:
```bash
[REDACTED_SECRET]
BLUEBUBBLES_PASSWORD=your-bluebubbles-password
```

4. Make the script executable:
```bash
chmod +x ~/.openclaw/workspace/skills/voice-memo-imessage/send-voice-memo.sh
```

## Usage

### Command Line
```bash
./send-voice-memo.sh "Hey, how's it going?" +1234567890
```

### From OpenClaw Agent
The agent can invoke this skill to send voice responses.

## How It Works

### The Working Formula

After extensive debugging, we discovered the exact parameters needed for native voice bubbles:

```bash
# 1. Generate TTS (ElevenLabs)
curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}" \
  -d '{"text": "...", "model_id": "eleven_turbo_v2_5"}'

# 2. Convert to Opus CAF @ 24kHz (REQUIRED format for iMessage)
afconvert input.mp3 output.caf -f caff -d opus@24000 -c 1

# 3. Send via BlueBubbles with EXACT parameters
curl -X POST ".../api/v1/message/attachment" \
  --form-string "chatGuid=any;-;+PHONE" \  # NOT iMessage;-; !
  -F "method=private-api" \                 # REQUIRED
  -F "isAudioMessage=true" \                # REQUIRED
  -F "attachment=@output.caf;type=audio/x-caf"
```

### Critical Parameters

| Parameter | Correct Value | Wrong Value | Effect |
|-----------|--------------|-------------|--------|
| chatGuid | `any;-;+PHONE` | `iMessage;-;+PHONE` | Wrong = API timeouts |
| method | `private-api` | omitted or `apple-script` | Wrong = file attachment instead of voice bubble |
| Audio format | Opus @ 24kHz | PCM @ 44.1kHz | Wrong = 0-second unplayable audio |

### Why Pre-Conversion?

BlueBubbles' built-in conversion uses **PCM @ 44.1kHz**, but iMessage voice memos require **Opus @ 24kHz**. Pre-converting with `afconvert` bypasses this issue.

## Performance

| Step | Time | Notes |
|------|------|-------|
| TTS | ~0.25s | ElevenLabs turbo model |
| Convert | ~0.04s | Native macOS afconvert |
| Send | ~0.15s | Local BlueBubbles API |
| **Total** | **~0.4s** | 🚀 |

## Configuration

### Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `ELEVENLABS_API_KEY` | Yes | - | Your ElevenLabs API key |
| `BLUEBUBBLES_PASSWORD` | Yes | - | BlueBubbles server password |
| `ELEVENLABS_VOICE_ID` | No | `21m00Tcm4TlvDq8ikWAM` | Voice ID (default: Rachel) |
| `ELEVENLABS_MODEL_ID` | No | `eleven_turbo_v2_5` | TTS model |
| `BLUEBUBBLES_URL` | No | `http://127.0.0.1:1234` | BlueBubbles server URL |

### Voice Options

Default voice is **Rachel** — a natural, expressive female voice. You can use any ElevenLabs voice by changing `ELEVENLABS_VOICE_ID`.

**Expressive tags** (for emotional delivery):
- `[laughs]` — Natural laughter
- `[sighs]` — Expressive sigh  
- `[excited]` — Energetic delivery

Example: `"[excited] Oh my god, it worked!"`

## Cost

- ~$0.04 per 30-second voice memo (ElevenLabs)
- 10 messages/day ≈ $12/month

## Troubleshooting

### Voice memo arrives as file attachment (not voice bubble)
- Ensure `method=private-api` is set
- Ensure BlueBubbles Private API is enabled and helper is connected
- Check API response for `"isAudioMessage": true`

### API times out
- Use `any;-;+PHONE` format (NOT `iMessage;-;+PHONE`)
- Restart BlueBubbles if consistently slow

### Audio is 0 seconds / unplayable
- Ensure pre-conversion to Opus @ 24kHz
- Verify format: `afinfo output.caf` should show `opus @ 24000 Hz`

## Related

- [BlueBubbles](https://bluebubbles.app) — iMessage bridge for non-Apple devices
- [ElevenLabs](https://elevenlabs.io) — AI voice synthesis
- [OpenClaw](https://github.com/openclaw/openclaw) — AI agent framework

## License

MIT License — see [LICENSE](LICENSE)

## Credits

Developed by Amy & Amz while debugging BlueBubbles voice memos at 4am. 🌙

Special thanks to the BlueBubbles team and the OpenClaw community.


path: references/VOICES.md
content: # ElevenLabs Voice Options

## Current Voice (Default)

**Rachel**
- Voice ID: `21m00Tcm4TlvDq8ikWAM`
- Character: Warm, natural, friendly
- Best for: Casual conversation, storytelling, friendly updates
- Model: `eleven_turbo_v2_5` (fast, cost-effective)

## Popular Alternative Voices

**Alice**
- Voice ID: `Xb7hH8MSUJpSbSDYk0k2`
- Character: Confident, energetic
- Best for: Exciting announcements, enthusiastic delivery

**Domi**
- Voice ID: `AZnzlk1XvdvUeBnXmlld`
- Character: Strong, authoritative
- Best for: Serious topics, commanding presence

**Lily**
- Voice ID: `pFZP5JQG7iQjIQuC4Bku`
- Character: Soft, gentle
- Best for: Calm updates, soothing messages

**Charlotte**
- Voice ID: `XB0fDUnXU5powFXDhCwa`
- Character: Expressive, dynamic
- Best for: Storytelling, dramatic readings

## Voice Settings

**Model Options:**
- `eleven_turbo_v2_5` — Fast, cost-effective (recommended)
- `eleven_multilingual_v2` — Supports multiple languages
- `eleven_monolingual_v1` — Higher quality, slower

**Expressive Tags (for all voices):**
- `[laughs]` — Natural laughter
- `[sighs]` — Expressive sigh
- `[gasps]` — Surprised reaction
- `[excited]` — Energetic delivery
- `[whispers]` — Quiet, intimate tone
- `[pause]` — Brief silence

**Usage in script:**
```bash
scripts/send-voice-memo.sh "[excited] This is amazing!" +14169060839
```

## Changing Voice

Override in `~/.openclaw/.env`:
```bash
ELEVENLABS_VOICE_ID=Xb7hH8MSUJpSbSDYk0k2  # Alice
```

Or pass directly in script if modified to accept voice parameter.

## Cost

- ~$0.04 per 30-second voice memo
- 10 voice memos/day ≈ $12/month
- Worth it for bringing Amz to life 🎤


path: scripts/send-voice-memo.sh
content: #!/bin/bash
#
# send-voice-memo.sh
# Convert text to native iMessage voice bubble via BlueBubbles
#
# Usage: ./send-voice-memo.sh "Your message here" [recipient_phone]
#
# Requirements:
#   - ELEVENLABS_API_KEY in ~/.openclaw/.env
#   - BLUEBUBBLES_PASSWORD in ~/.openclaw/.env
#   - afconvert (macOS built-in)
#   - BlueBubbles running locally with Private API enabled
#
# Working formula discovered 2026-02-19:
#   - Opus CAF @ 24kHz (pre-converted, not BlueBubbles converted)
#   - chatGuid: any;-;+PHONE (NOT iMessage;-;)
#   - method: private-api (REQUIRED for native bubble)
#   - isAudioMessage: true

set -euo pipefail

# ============================================================================
# Configuration
# ============================================================================

# Load environment variables
if [[ -f ~/.openclaw/.env ]]; then
    source ~/.openclaw/.env
else
    echo "❌ Error: ~/.openclaw/.env not found"
    exit 1
fi

# ElevenLabs settings
[REDACTED_SECRET]"
VOICE_ID="${ELEVENLABS_VOICE_ID:-21m00Tcm4TlvDq8ikWAM}"  # Rachel voice ID
MODEL_ID="${ELEVENLABS_MODEL_ID:-eleven_turbo_v2_5}"

# BlueBubbles settings
BLUEBUBBLES_URL="${BLUEBUBBLES_URL:-http://127.0.0.1:1234}"
BLUEBUBBLES_PASSWORD="${BLUEBUBBLES_PASSWORD:-}"

# Default recipient
DEFAULT_RECIPIENT="${IMESSAGE_RECIPIENT:-+14169060839}"

# Output directory
OUTPUT_DIR="${VOICE_MEMO_OUTPUT_DIR:-$HOME/.openclaw/workspace/skills/voice-memo-imessage/tmp}"
mkdir -p "$OUTPUT_DIR"

TIMESTAMP=$(date +%s)
MP3_FILE="$OUTPUT_DIR/tts_${TIMESTAMP}.mp3"
CAF_FILE="$OUTPUT_DIR/voice_${TIMESTAMP}.caf"

# ============================================================================
# Validate inputs
# ============================================================================

if [[ -z "${1:-}" ]]; then
    echo "Usage: $0 \"Your message here\" [recipient_phone]"
    echo ""
    echo "Example: $0 \"Hey, just checking in!\" +14169060839"
    exit 1
fi

TEXT="$1"
RECIPIENT="${2:-$DEFAULT_RECIPIENT}"

if [[ -z "$RECIPIENT" ]]; then
    echo "❌ Error: No recipient specified"
    exit 1
fi

if [[ -z "$ELEVENLABS_API_KEY" ]]; then
    echo "❌ Error: ELEVENLABS_API_KEY not found in .env"
    exit 1
fi

if [[ -z "$BLUEBUBBLES_PASSWORD" ]]; then
    echo "❌ Error: BLUEBUBBLES_PASSWORD not found in .env"
    exit 1
fi

# ============================================================================
# Step 1: Generate TTS audio (ElevenLabs)
# ============================================================================

echo "🎤 Generating voice memo..."

# Escape text for JSON
TEXT_ESCAPED=$(echo "$TEXT" | sed 's/"/\\"/g' | sed "s/'/\\'/g")

curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/$VOICE_ID" \
    -H "xi-[REDACTED_SECRET]" \
    -H "Content-Type: application/json" \
    -d "{
        \"text\": \"$TEXT_ESCAPED\",
        \"model_id\": \"$MODEL_ID\",
        \"voice_settings\": {
            \"stability\": 0.5,
            \"similarity_boost\": 0.75
        }
    }" \
    --output "$MP3_FILE" \
    --silent \
    --show-error

if [[ ! -f "$MP3_FILE" ]] || [[ ! -s "$MP3_FILE" ]]; then
    echo "❌ Error: TTS generation failed"
    exit 1
fi

MP3_SIZE=$(stat -f%z "$MP3_FILE" 2>/dev/null || stat -c%s "$MP3_FILE")
echo "✅ TTS: ${MP3_SIZE} bytes"

# ============================================================================
# Step 2: Convert to Opus CAF @ 24kHz (REQUIRED for native voice bubbles)
# ============================================================================

echo "🔄 Converting to Opus CAF..."

# This is the EXACT format iMessage expects for voice memos:
# - Opus codec (not PCM, not AAC)
# - 24000 Hz sample rate
# - Mono channel
# - CAF container
afconvert "$MP3_FILE" "$CAF_FILE" -f caff -d opus@24000 -c 1

if [[ ! -f "$CAF_FILE" ]] || [[ ! -s "$CAF_FILE" ]]; then
    echo "❌ Error: Conversion failed"
    exit 1
fi

CAF_SIZE=$(stat -f%z "$CAF_FILE" 2>/dev/null || stat -c%s "$CAF_FILE")
echo "✅ CAF: ${CAF_SIZE} bytes"

# ============================================================================
# Step 3: Send via BlueBubbles (working formula from 2026-02-19)
# ============================================================================

echo "📱 Sending to $RECIPIENT..."

# CRITICAL: chatGuid format is "any;-;+PHONE" NOT "iMessage;-;+PHONE"
# The "any" prefix works; "iMessage" causes issues
CHAT_GUID="any;-;$RECIPIENT"

# Send via BlueBubbles API with the WORKING formula:
# - method=private-api (REQUIRED for native voice bubble)
# - isAudioMessage=true (REQUIRED)
# - type=audio/x-caf on attachment
# - Pre-converted Opus CAF (don't let BlueBubbles convert)
RESPONSE=$(curl -X POST "$BLUEBUBBLES_URL/api/v1/message/attachment" \
    -H "Authorization: Bearer $BLUEBUBBLES_PASSWORD" \
    -F "attachment=@$CAF_FILE;type=audio/x-caf" \
    --form-string "chatGuid=$CHAT_GUID" \
    -F "name=voice.caf" \
    -F "tempGuid=temp-$TIMESTAMP" \
    -F "isAudioMessage=true" \
    -F "method=private-api" \
    --silent \
    --max-time 60)

# Check response
if echo "$RESPONSE" | grep -q '"isAudioMessage":true'; then
    echo "✅ Native voice bubble sent!"
    echo "📊 MP3: ${MP3_SIZE}B → CAF: ${CAF_SIZE}B"
    
    # Clean up both MP3 and CAF after successful send
    rm -f "$MP3_FILE" "$CAF_FILE"
    
    exit 0
elif echo "$RESPONSE" | grep -q '"status":200'; then
    echo "⚠️  Sent, but may not be native bubble (isAudioMessage not confirmed)"
    echo "Response: $RESPONSE"
    rm -f "$MP3_FILE" "$CAF_FILE"
    exit 0
else
    echo "❌ Error: Send failed"
    echo "Response: $RESPONSE"
    rm -f "$MP3_FILE" "$CAF_FILE"
    exit 1
fi


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)