Skill: obviyus/openrouter-transcribe (version 1.0.0)

--- name: openrouter-transcribe description: Transcribe audio files via OpenRouter using audio-capable models (Gemini, GPT-4o-audio, etc). homepage: https://openrouter.ai/docs metadata: {"clawdbot":{"emoji":"🎙️","requires":{"bins":["curl","ffmpeg","base64","jq"],"env":["OPENROUTER_API_KEY"]},"primaryEnv":"OPENROUTER_API_KEY"}} --- # OpenRouter Audio Transcription Transcribe audio files using OpenRouter's chat completions API with `input_audio` content type. Works with any audio-capable model. ## Quick start ```bash {baseDir}/scripts/transcribe.sh /path/to/audio.m4a ``` Output goes to stdout. ## Useful flags ```bash # Custom model (default: google/gemini-2.5-flash) {baseDir}/scripts/transcribe.sh audio.ogg --model openai/gpt-4o-audio-preview # Custom instructions {baseDir}/scripts/transcribe.sh audio.m4a --prompt "Transcribe with speaker labels" # Save to file {baseDir}/scripts/transcribe.sh audio.m4a --out /tmp/transcript.txt # Custom caller identifier (for OpenRouter dashboard) {baseDir}/scripts/transcribe.sh audio.m4a --title "MyApp" ``` ## How it works 1. Converts audio to WAV (mono, 16kHz) using ffmpeg 2. Base64 encodes the audio 3. Sends to OpenRouter chat completions with `input_audio` content 4. Extracts transcript from response ## API key Set `OPENROUTER_API_KEY` env var, or configure in `~/.clawdbot/clawdbot.json`: ```json5 { skills: { "openrouter-transcribe": { [REDACTED_SECRET]" } } } ``` ## Headers The script sends identification headers to OpenRouter: - `X-Title`: Caller name (default: "Peanut/Clawdbot") - `HTTP-Referer`: Reference URL (default: "https://clawdbot.com") These show up in your OpenRouter dashboard for tracking. ## Troubleshooting **ffmpeg format errors**: The script uses a temp directory (not `mktemp -t file.wav`) because macOS's mktemp adds random suffixes after the extension, breaking format detection. **Argument list too long**: Large audio files produce huge base64 strings that exceed shell argument limits. The script writes to temp files (`--rawfile` for jq, `@file` for curl) instead of passing data as arguments. **Empty response**: If you get "Empty response from API", the script will dump the raw response for debugging. Common causes: - Invalid API key - Model doesn't support audio input - Audio file too large or corrupted

path: scripts/transcribe.sh
content: #!/usr/bin/env bash
set -euo pipefail

usage() {
  cat >&2 <<'EOF'
Usage:
  transcribe.sh <audio-file> [options]

Options:
  --model <model>    OpenRouter model (default: google/gemini-2.5-flash)
  --prompt <text>    Custom transcription instructions
  --out <file>       Output file (default: stdout)
  --title <name>     Caller identifier for OpenRouter (default: Clawdbot)
  --referer <url>    HTTP-Referer header (default: https://clawdbot.com)

Example:
  transcribe.sh audio.m4a --prompt "Include timestamps"
EOF
  exit 2
}

if [[ "${1:-}" == "" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
  usage
fi

in="${1:-}"
shift || true

model="google/gemini-2.5-flash"
prompt="Please transcribe this audio file. Keep it readable, suitable for messaging. Begin transcript immediately without any commentary."
out=""
title="Clawdbot"
referer="https://clawdbot.com"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --model)
      model="${2:-}"
      shift 2
      ;;
    --prompt)
      prompt="${2:-}"
      shift 2
      ;;
    --out)
      out="${2:-}"
      shift 2
      ;;
    --title)
      title="${2:-}"
      shift 2
      ;;
    --referer)
      referer="${2:-}"
      shift 2
      ;;
    *)
      echo "Unknown arg: $1" >&2
      usage
      ;;
  esac
done

if [[ ! -f "$in" ]]; then
  echo "File not found: $in" >&2
  exit 1
fi

if [[ "${OPENROUTER_API_KEY:-}" == "" ]]; then
  echo "Missing OPENROUTER_API_KEY" >&2
  exit 1
fi

# Create temp files for conversion
tmp_dir=$(mktemp -d)
tmp_wav="$tmp_dir/audio.wav"
trap 'rm -rf "$tmp_dir"' EXIT

# Convert audio to WAV (mono, 16kHz) using ffmpeg
if ! ffmpeg -y -i "$in" -ac 1 -ar 16000 "$tmp_wav" 2>/dev/null; then
  echo "ffmpeg failed to convert audio" >&2
  exit 1
fi

# Base64 encode the WAV file
audio_base64=$(base64 < "$tmp_wav" | tr -d '\n')

# Build JSON payload using jq for proper escaping
# Write base64 to file first to avoid "argument list too long" errors
echo "$audio_base64" > "$tmp_dir/audio.b64"
payload_file="$tmp_dir/payload.json"
jq -n \
  --arg model "$model" \
  --arg prompt "$prompt" \
  --rawfile audio "$tmp_dir/audio.b64" \
  '{
    model: $model,
    messages: [{
      role: "user",
      content: [
        { type: "text", text: $prompt },
        { type: "input_audio", input_audio: { data: ($audio | rtrimstr("\n")), format: "wav" } }
      ]
    }]
  }' > "$payload_file"

# Make API request
response=$(curl -sS "https://openrouter.ai/api/v1/chat/completions" \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Title: $title" \
  -H "HTTP-Referer: $referer" \
  -d "@$payload_file")

# Check for API errors first
if echo "$response" | jq -e '.error' >/dev/null 2>&1; then
  error_msg=$(echo "$response" | jq -r '.error.message // .error // "Unknown API error"')
  echo "API error: $error_msg" >&2
  exit 1
fi

# Extract transcript
transcript=$(echo "$response" | jq -r '.choices[0].message.content // empty')

if [[ -z "$transcript" ]]; then
  echo "Empty response from API. Raw response:" >&2
  echo "$response" | head -c 500 >&2
  exit 1
fi

if [[ "$out" != "" ]]; then
  mkdir -p "$(dirname "$out")"
  echo "$transcript" > "$out"
  echo "$out"
else
  echo "$transcript"
fi


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