Skill: truenight/whisper-cpp (version 1.0.2)

--- name: whisper-cpp description: Install and use whisper.cpp (local, free/offline speech-to-text) with OpenClaw. Supports downloading different ggml model sizes (tiny/base/small/medium/large-*) and configuring tools.media.audio to transcribe inbound voice notes without paid provider APIs. --- # whisper-cpp (Local Whisper STT for OpenClaw) This skill sets up **local whisper.cpp** STT for inbound Telegram voice notes. ## Dependencies You need build tools (`git`, `cmake`, compiler toolchain) + `curl` and `ffmpeg` (to decode Telegram OGG/Opus → WAV). ## Install From this skill directory: ```bash bash scripts/install_whisper_cpp.sh bash scripts/download_models.sh bash scripts/install_wrapper.sh bash scripts/patch_openclaw_audio.sh ``` Send a Telegram voice note to test. ## Tuning ### Model choice This setup uses **ggml Whisper models** stored in `~/.cache/whisper`. Common model names you can download: - `tiny`, `base`, `small`, `medium` - `large-v1`, `large-v2`, `large-v3` (bigger/slower, usually more accurate) By default we download: `base` + `small`. To download specific models: ```bash bash scripts/download_models.sh tiny base small ``` For the OpenClaw wrapper, you can select: ```bash OPENCLAW_WHISPER_MODEL=small openclaw-whisper-stt /path/to/audio ``` - Default language: auto-detect (`OPENCLAW_WHISPER_LANG=auto`) - Force a language (example): ```bash OPENCLAW_WHISPER_LANG=en openclaw-whisper-stt /path/to/audio ``` Models are stored in: `~/.cache/whisper`. ## Cleanup (optional) After install (whisper-cli + libs are in `~/.local/`): ```bash bash scripts/cleanup_build.sh ``` ## Troubleshooting Confirm OpenClaw is using the wrapper: ```bash which openclaw-whisper-stt openclaw config get tools.media.audio.models ``` Test the wrapper directly: ```bash openclaw-whisper-stt /path/to/audio.ogg OPENCLAW_WHISPER_MODEL=small openclaw-whisper-stt /path/to/audio.ogg ``` Follow gateway logs while sending a Telegram voice note: ```bash openclaw logs --follow ``` ## Files - Wrapper source: `bin/openclaw-whisper-stt.sh` (linked to `~/.local/bin/openclaw-whisper-stt`) - OpenClaw config patcher: `scripts/patch_openclaw_audio.sh`

path: bin/openclaw-whisper-stt.sh
content: #!/usr/bin/env bash
set -euo pipefail

MEDIA_PATH="${1:?usage: openclaw-whisper-stt <audio-file>}"
MODEL_NAME="${OPENCLAW_WHISPER_MODEL:-base}"
LANG_NAME="${OPENCLAW_WHISPER_LANG:-auto}"

MODELS_DIR="$HOME/.cache/whisper"
WHISPER_CLI="$HOME/.local/bin/whisper-cli"

# ensure locally-installed whisper.cpp shared libs are discoverable
export LD_LIBRARY_PATH="$HOME/.local/lib:${LD_LIBRARY_PATH:-}"

case "$MODEL_NAME" in
  base|small) ;;
  *) echo "OPENCLAW_WHISPER_MODEL must be base|small (got: $MODEL_NAME)" >&2; exit 2;;
 esac

case "$LANG_NAME" in
  auto|en|es|pt|ru|fr|de|it|zh|ja|ko|ar|hi) ;;
  *) echo "OPENCLAW_WHISPER_LANG should be a Whisper language code like auto|en|ru (got: $LANG_NAME)" >&2;;
esac

MODEL_PATH="$MODELS_DIR/ggml-$MODEL_NAME.bin"
[ -f "$MODEL_PATH" ] || { echo "Model not found: $MODEL_PATH" >&2; exit 3; }

TMP_WAV=""
cleanup() {
  if [ -n "$TMP_WAV" ] && [ -f "$TMP_WAV" ]; then rm -f "$TMP_WAV"; fi
}
trap cleanup EXIT

INPUT="$MEDIA_PATH"
# Telegram voice notes are commonly OGG+Opus. Convert to 16kHz mono PCM wav for whisper-cli.
case "${MEDIA_PATH,,}" in
  *.ogg|*.opus|*.m4a|*.mp3|*.flac|*.webm)
    TMP_WAV="$(mktemp -t openclaw-whisper-XXXXXX.wav)"
    ffmpeg -hide_banner -loglevel error -y -i "$MEDIA_PATH" -ar 16000 -ac 1 -c:a pcm_s16le "$TMP_WAV"
    INPUT="$TMP_WAV"
    ;;
  *.wav) ;;
  *) ;;
esac

"$WHISPER_CLI" -m "$MODEL_PATH" -l "$LANG_NAME" -f "$INPUT" -nt -np


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

# Patch OpenClaw config to use local whisper.cpp wrapper for inbound audio STT.
# Uses `openclaw config set` (works with the current CLI).

WRAPPER_PATH="${WRAPPER_PATH:-$HOME/.local/bin/openclaw-whisper-stt}"

if [ ! -x "$WRAPPER_PATH" ]; then
  echo "Wrapper not found or not executable: $WRAPPER_PATH" >&2
  exit 2
fi

if ! command -v openclaw >/dev/null 2>&1; then
  echo "openclaw CLI not found on PATH" >&2
  exit 3
fi

echo "==> Setting tools.media.audio to use local whisper.cpp wrapper"

openclaw config set tools.media.audio.enabled true
openclaw config set tools.media.audio.maxBytes 20971520
openclaw config set --strict-json tools.media.audio.models "[
  {
    \"type\": \"cli\",
    \"command\": \"$WRAPPER_PATH\",
    \"args\": [\"{{MediaPath}}\"],
    \"timeoutSeconds\": 120
  }
]"

echo "==> Restarting gateway to apply config"
openclaw gateway restart >/dev/null

echo "[OK] Patched + restarted. Send a voice note to test."


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

# Removes whisper.cpp build/repo directories to save space.
# Safe if you've already installed whisper-cli + libs into ~/.local.

REPO="$HOME/.local/share/whisper.cpp/repo"

# Remove the repo (includes build output). Keep ~/.local/bin + ~/.local/lib + ~/.cache/whisper.
rm -rf "$REPO" || true

echo "[OK] Removed: $REPO"


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

# Installs the wrapper into ~/.local/bin for stable PATH + systemd usage.
# Creates ONE symlink (no extension) pointing at the skill's wrapper source.

SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC_REL="$SKILL_DIR/bin/openclaw-whisper-stt.sh"
SRC="$(readlink -f "$SRC_REL")"

DST_DIR="$HOME/.local/bin"
DST_PRIMARY="$DST_DIR/openclaw-whisper-stt"

mkdir -p "$DST_DIR"

# Ensure the target is executable (symlink itself doesn't carry exec perms).
chmod +x "$SRC"

# Primary symlink (no extension)
ln -sf "$SRC" "$DST_PRIMARY"

# Sanity checks
[ -L "$DST_PRIMARY" ] || { echo "Expected symlink: $DST_PRIMARY" >&2; exit 2; }
[ -x "$DST_PRIMARY" ] || { echo "Not executable: $DST_PRIMARY" >&2; exit 3; }

echo "[OK] Linked: $DST_PRIMARY -> $SRC"


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

# Builds whisper.cpp and installs whisper-cli into ~/.local/bin,
# and installs the required shared libraries into ~/.local/lib.

PREFIX="$HOME/.local/share/whisper.cpp"
REPO="$PREFIX/repo"
BUILD="$REPO/build"

mkdir -p "$PREFIX" "$HOME/.local/bin" "$HOME/.local/lib"

if [ ! -d "$REPO/.git" ]; then
  git clone https://github.com/ggerganov/whisper.cpp "$REPO"
else
  git -C "$REPO" pull --ff-only
fi

cmake -S "$REPO" -B "$BUILD" -DCMAKE_BUILD_TYPE=Release
cmake --build "$BUILD" -j"$(nproc)"

# install whisper-cli
if [ -x "$BUILD/bin/whisper-cli" ]; then
  install -m 755 "$BUILD/bin/whisper-cli" "$HOME/.local/bin/whisper-cli"
elif [ -x "$BUILD/whisper-cli" ]; then
  install -m 755 "$BUILD/whisper-cli" "$HOME/.local/bin/whisper-cli"
else
  echo "Could not find whisper-cli under: $BUILD" >&2
  exit 1
fi

# install shared libs so whisper-cli keeps working after you delete the build dir/repo
find "$BUILD" -maxdepth 4 -type f \( -name 'libwhisper.so*' -o -name 'libggml*.so*' \) \
  -exec install -m 755 -t "$HOME/.local/lib" {} +

cd "$HOME/.local/lib"
# create symlinks expected by the loader (best-effort)
ls -1 libwhisper.so.1.* >/dev/null 2>&1 && [ -e libwhisper.so.1 ] || ln -sf "$(ls -1 libwhisper.so.1.* | head -n1)" libwhisper.so.1
ls -1 libggml.so.0.* >/dev/null 2>&1 && [ -e libggml.so.0 ] || ln -sf "$(ls -1 libggml.so.0.* | head -n1)" libggml.so.0
ls -1 libggml-base.so.0.* >/dev/null 2>&1 && [ -e libggml-base.so.0 ] || ln -sf "$(ls -1 libggml-base.so.0.* | head -n1)" libggml-base.so.0
ls -1 libggml-cpu.so.0.* >/dev/null 2>&1 && [ -e libggml-cpu.so.0 ] || ln -sf "$(ls -1 libggml-cpu.so.0.* | head -n1)" libggml-cpu.so.0

LD_LIBRARY_PATH="$HOME/.local/lib" "$HOME/.local/bin/whisper-cli" -h >/dev/null

echo "[OK] whisper-cli installed to ~/.local/bin and libs installed to ~/.local/lib"


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