← All Skills

snappy-voice-control

v1.0.0
6 files, 192.3 KB ~17,022 words · 69 min read Updated 2026-09-09

snappy-voice-control skill

37 of 45 checks pass
What it can do
devicesread
permissionsread
pickread
record secs? out-path?write-reversible
stream-urlwrite-reversible
transcribe audio-file?write-reversible
What does not pass yet
Architecture 1 endpoints⚗ parallel
api.deepgram.com1 endpoint
POST/v1/listen
$ npx snappy-skills install snappy-voice-control
zip ↓
File Tree
├── AGENTS.md ├── SKILL.md ├── api.ts ├── references/ │ ├── extract-agent-hotword.md │ └── extract-fazm-voice.md └── refusals.test.ts
Documents
AGENTS.md

snappy-voice-control — Agent Loader#

Two shipping open-source Mac agents solved voice input the hard way; this skill is

what they learned, cited to source. Push-to-talk (fazm): hold a modifier, batch

Deepgram after release, no false triggers. Hotword (Agent!): "Agent!" scanned

out of SFSpeechRecognizer partials, silence measured as unchanged text length.

Neither uses VAD. Every "the mic doesn't work" traces to §1 (the default input is a

Bluetooth or virtual device) or §6 (AXIsProcessTrusted lies) of SKILL.md.

API module#

typescriptimport { listInputDevices, pickPhysicalInput, record, transcribeFile, deepgramStreamUrl, checkPermissions } from "../snappy-voice-control/api.ts";

Or CLI:

bashnpx tsx ~/.claude/skills/snappy-voice-control/api.ts devices --json
npx tsx ~/.claude/skills/snappy-voice-control/api.ts pick
npx tsx ~/.claude/skills/snappy-voice-control/api.ts record 5 /tmp/take.wav --device "MacBook Pro Microphone"
npx tsx ~/.claude/skills/snappy-voice-control/api.ts transcribe /tmp/take.wav --lang en --keyterm Snappy
npx tsx ~/.claude/skills/snappy-voice-control/api.ts stream-url --lang multi --keyterm Snappy --keyterm Xano
npx tsx ~/.claude/skills/snappy-voice-control/api.ts permissions

API functions#

Function Purpose
listInputDevices() Every input device with transport (builtin/usb/bluetooth/virtual/aggregate), default flag, sample rate, ffmpeg index. Zero deps (system_profiler).
pickPhysicalInput(devices?) fazm's rule: skip virtual/aggregate; built-in > USB > Bluetooth > any non-virtual.
record(seconds, outPath?, {device?}) ffmpeg -f avfoundation → 16 kHz mono Int16 WAV — the exact format Deepgram linear16 wants.
transcribeFile(path, {language?, keyterms?}) Deepgram REST batch, nova-3, fazm's proven params; drops ≥4-identical-token hallucinations. Uses env("DEEPGRAM_API_KEY").
deepgramStreamUrl({language?, keyterms?, channels?}) Pure: the wss:// URL with endpointing=300&utterance_end_ms=1000&interim_results=true… exactly as shipped.
checkPermissions() Accessibility trust via a ctypes AXIsProcessTrusted call (no PyObjC); Microphone/Speech reported as unreadable without Full Disk Access, with the deep links to fix.

Rules#

  • Never build capture on AVAudioEngine for a product that plays audio — aggregate device → Bluetooth A2DP/SCO degradation. HAL IOProc, serial queue, 0.3 s settle on device change.
  • Never trust the system default input blindly; check transport type (this MacBook's default is AirPods; a virtual krisp microphone exists).
  • PTT on a bare modifier needs the 200 ms delay + keyDown cancel, or every Ctrl+C becomes a press.
  • Batch STT is the default for accuracy; streaming is opt-in. Vocab param is keyterm, not keywords.
  • AXIsProcessTrusted() can be stale (macOS 26, re-signs) — confirm with a real AX call and a listen-only CGEvent tap.
  • transcribeFile fails visibly without DEEPGRAM_API_KEY; there is no fallback path.
  • Recording uses the live mic on this Mac — say so before recording in someone's presence.

Uses#

snappy-ax (accessibility tree control) · snappy-agent-host (Claude Code / Codex / Gemini inside the app) · snappy-video (Whisper captioning) · snappy-cleanshot · macos-patterns · swift-concurrency.

Show produced work with snappy-faces: call draw for image channels or lang for MCP Apps.

<!-- SKILL-INDEX-START -->

[snappy-voice-control Index]|root: ~/.claude/skills/snappy-voice-control|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}|references:{extract-agent-hotword.md,extract-fazm-voice.md}

<!-- SKILL-INDEX-END -->

Used by#

  • snappy-ax

<!-- SNAPPY-CONTRACT-VERBS-START -->

Contract verbs#

Generated from api.ts HAND_CONTRACT. Do not hand-edit this block.

Verb Contract arguments Effect First call
devices read npx tsx ~/.claude/skills/snappy-voice-control/api.ts devices
permissions read npx tsx ~/.claude/skills/snappy-voice-control/api.ts permissions
pick read npx tsx ~/.claude/skills/snappy-voice-control/api.ts pick
record secs?, out-path? write-reversible npx tsx ~/.claude/skills/snappy-voice-control/api.ts record
stream-url write-reversible npx tsx ~/.claude/skills/snappy-voice-control/api.ts stream-url
transcribe audio-file? write-reversible npx tsx ~/.claude/skills/snappy-voice-control/api.ts transcribe

Show the result#

When an answer carries face_hint, show it with one snappy_present(<answer>) call.

See /snappy-faces for face selection. Human-facing images must crop to the

element, render at 2x on Retina, and fill the destination channel instead of

placing a small card in a full-page screenshot.

<!-- SNAPPY-CONTRACT-VERBS-END -->

---
name: snappy-voice-control
role: Voice input for native Mac apps — mic capture, push-to-talk, hotword, Deepgram STT, turn-taking, and the TCC lies — extracted with citations from fazm and Agent!
loaded-by: PreToolUse hook (auto-injected when "snappy-voice-control" is mentioned)
---

# snappy-voice-control — Agent Loader

Two shipping open-source Mac agents solved voice input the hard way; this skill is
what they learned, cited to source. **Push-to-talk** (fazm): hold a modifier, batch
Deepgram after release, no false triggers. **Hotword** (Agent!): "Agent!" scanned
out of `SFSpeechRecognizer` partials, silence measured as unchanged text length.
Neither uses VAD. Every "the mic doesn't work" traces to §1 (the default input is a
Bluetooth or virtual device) or §6 (`AXIsProcessTrusted` lies) of SKILL.md.

## API module

```typescript
import { listInputDevices, pickPhysicalInput, record, transcribeFile, deepgramStreamUrl, checkPermissions } from "../snappy-voice-control/api.ts";
```

Or CLI:
```bash
npx tsx ~/.claude/skills/snappy-voice-control/api.ts devices --json
npx tsx ~/.claude/skills/snappy-voice-control/api.ts pick
npx tsx ~/.claude/skills/snappy-voice-control/api.ts record 5 /tmp/take.wav --device "MacBook Pro Microphone"
npx tsx ~/.claude/skills/snappy-voice-control/api.ts transcribe /tmp/take.wav --lang en --keyterm Snappy
npx tsx ~/.claude/skills/snappy-voice-control/api.ts stream-url --lang multi --keyterm Snappy --keyterm Xano
npx tsx ~/.claude/skills/snappy-voice-control/api.ts permissions
```

## API functions

| Function | Purpose |
|----------|---------|
| `listInputDevices()` | Every input device with transport (builtin/usb/bluetooth/virtual/aggregate), default flag, sample rate, ffmpeg index. Zero deps (`system_profiler`). |
| `pickPhysicalInput(devices?)` | fazm's rule: skip virtual/aggregate; built-in > USB > Bluetooth > any non-virtual. |
| `record(seconds, outPath?, {device?})` | `ffmpeg -f avfoundation` → 16 kHz mono Int16 WAV — the exact format Deepgram `linear16` wants. |
| `transcribeFile(path, {language?, keyterms?})` | Deepgram REST batch, `nova-3`, fazm's proven params; drops ≥4-identical-token hallucinations. Uses `env("DEEPGRAM_API_KEY")`. |
| `deepgramStreamUrl({language?, keyterms?, channels?})` | Pure: the `wss://` URL with `endpointing=300&utterance_end_ms=1000&interim_results=true…` exactly as shipped. |
| `checkPermissions()` | Accessibility trust via a ctypes `AXIsProcessTrusted` call (no PyObjC); Microphone/Speech reported as unreadable without Full Disk Access, with the deep links to fix. |

## Rules
- **Never** build capture on `AVAudioEngine` for a product that plays audio — aggregate device → Bluetooth A2DP/SCO degradation. HAL IOProc, serial queue, 0.3 s settle on device change.
- **Never** trust the system default input blindly; check transport type (this MacBook's default is AirPods; a virtual `krisp microphone` exists).
- PTT on a bare modifier needs the 200 ms delay + keyDown cancel, or every Ctrl+C becomes a press.
- Batch STT is the default for accuracy; streaming is opt-in. Vocab param is `keyterm`, not `keywords`.
- `AXIsProcessTrusted()` can be stale (macOS 26, re-signs) — confirm with a real AX call and a listen-only CGEvent tap.
- `transcribeFile` fails visibly without `DEEPGRAM_API_KEY`; there is no fallback path.
- Recording uses the live mic on this Mac — say so before recording in someone's presence.

## Uses
`snappy-ax` (accessibility tree control) · `snappy-agent-host` (Claude Code / Codex / Gemini inside the app) · `snappy-video` (Whisper captioning) · `snappy-cleanshot` · `macos-patterns` · `swift-concurrency`.


Show produced work with `snappy-faces`: call `draw` for image channels or `lang` for MCP Apps.

<!-- SKILL-INDEX-START -->
[snappy-voice-control Index]|root: ~/.claude/skills/snappy-voice-control|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}|references:{extract-agent-hotword.md,extract-fazm-voice.md}
<!-- SKILL-INDEX-END -->

## Used by

- `snappy-ax`

<!-- SNAPPY-CONTRACT-VERBS-START -->
## Contract verbs

Generated from `api.ts` `HAND_CONTRACT`. Do not hand-edit this block.

| Verb | Contract arguments | Effect | First call |
|---|---|---|---|
| `devices` | — | `read` | `npx tsx ~/.claude/skills/snappy-voice-control/api.ts devices` |
| `permissions` | — | `read` | `npx tsx ~/.claude/skills/snappy-voice-control/api.ts permissions` |
| `pick` | — | `read` | `npx tsx ~/.claude/skills/snappy-voice-control/api.ts pick` |
| `record` | `secs?`, `out-path?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-voice-control/api.ts record` |
| `stream-url` | — | `write-reversible` | `npx tsx ~/.claude/skills/snappy-voice-control/api.ts stream-url` |
| `transcribe` | `audio-file?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-voice-control/api.ts transcribe` |

## Show the result

When an answer carries `face_hint`, show it with one `snappy_present(<answer>)` call.
See `/snappy-faces` for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->

Keyboard Shortcuts

Search in document⌘K
Focus search/
Previous file tab
Next file tab
Close overlayEsc
Show shortcuts?