apps/swabble/README.md
swabble is a Swift 6.2 wake-word hook daemon. The CLI targets macOS 26 (SpeechAnalyzer + SpeechTranscriber). The shared SwabbleKit target is multi-platform and exposes wake-word gating utilities for iOS/macOS apps.
clawd (aliases claude), optional --no-wake bypass.# Install deps
brew install swiftformat swiftlint
# Build
swift build
# Write default config (~/.config/swabble/config.json)
swift run swabble setup
# Run foreground daemon
swift run swabble serve
# Test your hook
swift run swabble test-hook "hello world"
# Transcribe a file to SRT
swift run swabble transcribe /path/to/audio.m4a --format srt --output out.srt
Add swabble as a SwiftPM dependency and import the Swabble or SwabbleKit product:
// Package.swift
dependencies: [
.package(url: "https://github.com/steipete/swabble.git", branch: "main"),
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "Swabble", package: "swabble"), // Speech pipeline (macOS 26+ / iOS 26+)
.product(name: "SwabbleKit", package: "swabble"), // Wake-word gate utilities (iOS 17+ / macOS 15+)
]),
]
serve — foreground loop (mic → wake → hook)transcribe <file> — offline transcription (txt|srt)test-hook "text" — invoke configured hookmic list|set <index> — enumerate/select input devicesetup — write default config JSONdoctor — check Speech auth & device availabilityhealth — prints oktail-log — last 10 transcriptsstatus — show wake state + recent transcriptsservice install|uninstall|status — user launchd plist (stub: prints launchctl commands)start|stop|restart — placeholders until full launchd wiringAll commands accept Commander runtime flags (-v/--verbose, --json-output, --log-level), plus --config where applicable.
~/.config/swabble/config.json (auto-created by setup):
{
"audio": {"deviceName": "", "deviceIndex": -1, "sampleRate": 16000, "channels": 1},
"wake": {"enabled": true, "word": "clawd", "aliases": ["claude"]},
"hook": {
"command": "",
"args": [],
"prefix": "Voice swabble from ${hostname}: ",
"cooldownSeconds": 1,
"minCharacters": 24,
"timeoutSeconds": 5,
"env": {}
},
"logging": {"level": "info", "format": "text"},
"transcripts": {"enabled": true, "maxEntries": 50},
"speech": {"localeIdentifier": "en_US", "etiquetteReplacements": false}
}
--config /path/to/config.json on relevant commands.~/Library/Application Support/swabble/transcripts.log.When a wake-gated transcript passes min_chars & cooldown, swabble runs:
<command> <args...> "<prefix><text>"
Environment variables:
SWABBLE_TEXT — stripped transcript (wake word removed)SWABBLE_PREFIX — rendered prefix (hostname substituted)hook.env key/valuesAVAudioEngine tap → BufferConverter → AnalyzerInput → SpeechAnalyzer with a SpeechTranscriber module../scripts/format.sh (uses local .swiftformat)./scripts/lint.sh (uses local .swiftlint.yml)swift test (uses swift-testing package)