Voice-first agentic coding with a Teenage Engineering EP-2350 "ting".
Squeeze the ting's trigger and talk — your words appear live in whatever app is focused (Claude Code, Codex, anything). Release to finish, tap orange to send, tap green to scrap the take, tap white to summon your agent to the front. It's a physical push-to-talk button for coding with agents: pick up the mic, say what you want, put it down.
Under the hood, a tiny MicroPython event engine installs onto the ting's own disk in one click (no firmware modification, fully reversible). Button presses reach your Mac instantly over USB, or — with no USB attached, the ting on batteries — over an inaudible ultrasonic chirp protocol on the 3.5mm audio cable. You never think about any of that; you just squeeze and talk.
macOS only. Live dictation requires macOS 26 (Tahoe) or later — it uses
Apple's on-device SpeechAnalyzer. Everything else (button macros,
keystrokes, shell actions) works on macOS 13+. Apple Silicon or Intel. You
also need the hardware below.
You need:
- A Teenage Engineering EP-2350 "ting" — the lo-fi FX microphone.
- A USB line-in adapter. The ting's curly cable outputs line-level audio, and Mac headphone jacks don't accept line-in — plugging the ting straight into your laptop's 3.5mm port will not work. Any USB audio interface with a line input is fine; two that are tested and work well: the Sonos Line-In adapter (smallest option — a bare USB-C dongle) and the Cubilux USB-C line-in adapter (bulkier; adds mic-in + headphone-out).
- Optionally, a USB-C cable to the ting for docked use (instant button events, battery readout) and for the one-time flash.
Wiring: ting line-out → adapter line-in port → Mac USB. Turn the green volume knob under the ting's lid up to around halfway.
brew install --cask tutorintelligence/tap/tingleYou need Xcode Command Line Tools (xcode-select --install) and macOS 26+
for dictation.
git clone https://github.com/tutorintelligence/tingle.git
cd tingle
swift build -c release
./scripts/bundle.sh
cp -r dist/tingle.app /Applications/
open /Applications/tingle.appOr just paste this to Claude Code / Codex and let it do everything:
Install tingle with
brew install --cask tutorintelligence/tap/tingleand launch it. Then tell me what the menu bar shows and walk me through flashing my ting and granting the microphone and accessibility permissions.
- Launch tingle — a striped-circle icon appears in the menu bar.
- Plug the ting in over USB-C and choose Flash EP… from the menu. This writes four inaudible signal tones and the event engine onto the ting's disk (existing contents are backed up first). Power-cycle the ting when prompted: press the small button above its USB-C port, then push the handle to start it.
- Grant the two permission prompts (microphone, accessibility) — tingle asks at launch, and the menu bar icon wears an orange "!" badge with fix-it menu items until both are granted.
- Squeeze the trigger and talk. Words appear where your cursor is.
The menu bar dot tells you everything: green = ting present (USB, or heard by its ultrasonic heartbeat), orange = searching, red = trigger held / dictating, none = no ting around. Input-device selection is automatic — tingle finds whichever line-in the ting is plugged into by listening for its heartbeat.
| Input | Action |
|---|---|
| squeeze handle | dictate live into the focused app; release to finish |
| orange button | enter (send it) |
| green button | erase the last dictated take (repeat for earlier takes) |
| white button | summon your agent — brings the first running AI coding app (Claude, Codex, Cursor, iTerm2, Terminal) to the front |
Every one of these is just a default. tingle is configurable by design — the summon list, what each button does, the dictation vocabulary, the rewrite pass — but the defaults are meant to be good enough that most people never open the config.
Two files in ~/Library/Application Support/tingle/:
default-config.toml holds every option, documented, and is refreshed by
the app on every launch — it's the reference, so new options and new
built-in vocabulary arrive with updates. Your own config.toml ("Edit
config…" in the menu) contains only what you change: copy any key over,
edit it, save — it wins over the default and reloads live.
extraVocabulary = ["Metabase", "kubectl"] # your jargon, added onto the built-in list
[replacements] # fix what biasing can't
"Tamil" = "TOML"
[mappings] # merges per key with the defaults
mode1 = { type = "shell", command = """
open -a "Claude" # multiline scripts welcome
""" }Actions: dictate, eraseDictation, keystroke, keyHold (held until you
release the trigger), and shell. The white button gives you four mappable
slots (mode1–mode4), selected by the ting's green mode LED.
Dictation uses Apple's on-device speech stack (macOS 26+). Everything else works on macOS 13+.
With Apple Intelligence available, an on-device LLM polishes each take a
moment after you release (on by default; [rewrite] in the config): fillers
gone, punctuation fixed, your jargon corrected — all locally, nothing
leaves the Mac, and it never touches your words' meaning (degenerate model
output is rejected and your original text stays).
About the orange microphone icon: macOS shows its privacy indicator whenever any app captures audio input — tingle is always listening to the line-in for the ting's chirps, so the icon is expected and can't be hidden. The "mic mode" options macOS offers there (Voice Isolation etc.) only affect video-conferencing-style apps; tingle's raw capture ignores them.
The ting executes user Python from its USB disk at boot. tingle ships an event engine that chains the stock firmware behavior, then reports button and trigger events as serial lines (over USB) and as ultrasonic codewords (over the 3.5mm audio cable): every event is four 25ms chirp symbols carrying an error-correcting code, matched-filter decoded on the Mac — a corrupted symbol self-corrects, and noise can never turn one button into another. A state-carrying heartbeat every 2 seconds acts as a pilot signal: it powers zero-config device discovery, self-healing state sync, and a decoder lock that keeps a sleeping or absent ting perfectly silent. Details and the measured hardware reference: DESIGN.md.
swift run tingle # run the menu bar app (dev build)
swift run tingle-tests # unit tests
python3 tools/test_payload.py # device event-engine tests
scripts/bundle.sh # assemble dist/tingle.appAgent-oriented contributor docs: CLAUDE.md (symlinked as AGENTS.md).
MIT © Tutor Intelligence