* refactor(apple): deslop shared Swift packages second pass Consolidate shared chat parsing, request projection, UI helpers, discovery, and speech lifecycle owners without changing public API, wire encodings, or visible strings. Preserve queued-message safety across pending capability mutations. Also prevent speech directive integer overflow, refresh cached audio converters after input format changes, and cancel stale settings dispatch after awaited outbox parking. Regression controls fail before these repairs and pass afterward. * fix(apple): preserve native lint contracts in shared cleanup * test(apple): bind chat history fixtures to lifecycle state * refactor(apple): keep tool projection with its event handler * refactor(apple): reuse chat view projection helpers * refactor(apple): retain native editor observer placement |
||
|---|---|---|
| .. | ||
| docs | ||
| scripts | ||
| Sources | ||
| Tests | ||
| .gitignore | ||
| .swiftlint.yml | ||
| CHANGELOG.md | ||
| LICENSE | ||
| Package.swift | ||
| README.md | ||
🎙️ swabble — Speech.framework wake-word hook daemon (macOS 26)
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.
- Local-only: Speech.framework on-device models; zero network usage.
- Wake word: Default
clawd(aliasesclaude), optional--no-wakebypass. - SwabbleKit: Shared wake gate utilities (gap-based gating when you provide speech segments).
- Hooks: Run any command with prefix/env, cooldown, min_chars, timeout.
- Services: launchd helper stubs for start/stop/install.
- File transcribe: TXT or SRT with time ranges (using AttributedString splits).
Quick start
# Install the repository-pinned Swift tools
../../scripts/install-swift-tools.sh .build/swift-tools
export PATH="$PWD/.build/swift-tools:$PATH"
# 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
Use as a library
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+)
]),
]
CLI
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— printsoktail-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 wiring
All commands accept Commander runtime flags (-v/--verbose, --json-output, --log-level), plus --config where applicable.
Config
~/.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 override:
--config /path/to/config.jsonon relevant commands. - Transcripts persist to
~/Library/Application Support/swabble/transcripts.log.
Hook protocol
serve runs the hook when a wake-gated transcript meets hook.minCharacters after wake-word removal and hook.cooldownSeconds has elapsed since the previous hook completed:
<command> <args...> "<prefix><text>"
test-hook invokes the configured hook directly, including short text, for manual verification.
Environment variables:
SWABBLE_TEXT— stripped transcript (wake word removed)SWABBLE_PREFIX— rendered prefix (hostname substituted)- plus any
hook.envkey/values
Speech pipeline
AVAudioEnginetap →BufferConverter→AnalyzerInput→SpeechAnalyzerwith aSpeechTranscribermodule.- Requests volatile + final results; the CLI uses text-only wake gating today.
- Authorization requested at first start; requires macOS 26 + new Speech.framework APIs.
Development
- Format:
./scripts/format.sh(uses the repository SwiftFormat policy) - Lint:
./scripts/lint.sh(uses local.swiftlint.yml) - Tests:
swift test(XCTest)
Roadmap
- launchd control (load/bootout, PID + status socket)
- JSON logging + PII redaction toggle
- Stronger wake-word detection and control socket status/health