README
¶
sonosh
sonosh is a keyboard-driven terminal UI for Sonos.
It keeps the most common controls close to the keyboard: switch rooms, browse the queue, inspect what is playing, and trigger playback without reaching for a browser or phone.

sonosh at a glance
- room switching
- now playing and queue inspection
- playback controls
- media and playlist browsing
- high-quality album art rendering in the player, with block-image fallbacks when needed
- RTL/bidirectional text rendering for right-to-left metadata in the terminal UI
- macOS media-key binding through the Swift helper for play/pause, next, previous, toggle, and volume up/down (volume keys require Accessibility permission)
Sonos CLI and backend
The sonosh CLI is documented below.
Install / build
Install with Homebrew:
brew install shlomiuziel/tap/sonosh
This installs the sonosh command globally. On macOS, the formula also builds
the Swift media helper and wires it up automatically so playback control keys
and now-playing metadata keep working without extra setup.
Upgrade later:
brew upgrade shlomiuziel/tap/sonosh
First-time Spotify setup:
- If you want Spotify / SMAPI search in
sonosh, use the inheritedsonoshCLI:sonosh auth smapi begin --service "Spotify"and finish the DeviceLink/AppLink flow. - Use
sonosh config set defaultRoom "<Room Name>"andsonosh config set defaultTimeout 20sif you want sticky defaults for the TUI and CLI. - See
docs/commands/sonos-auth-smapi.mdanddocs/commands/sonos-config.mdfor the full command details.
Install from source (Go):
go install github.com/shlomiuziel/sonosh/cmd/sonosh@latest
sonosh
Build sonosh locally:
go build -o sonosh ./cmd/sonosh
./sonosh
Build the optional macOS media helper:
swift build --package-path helpers/macos/sonosh-helper --configuration release
./sonosh --mac-helper-path helpers/macos/sonosh-helper/.build/release/sonosh-macos-helper
Build sonosh locally:
go build -o sonosh ./cmd/sonosh
./sonosh --version
Useful TUI keys:
up/downork/j: move selectionspace: play/pauses: stopn/p: next / previous+/-: volumem: mute/ortab: searchenter: search or play selected resultr: refreshq: quit
Docker:
docker build -t sonosh .
docker run --rm --network host -v "$PWD/.sonoscli:/data" sonosh discover
Linux containers need --network host for SSDP/UPnP discovery. The image includes ffmpeg, yt-dlp, and curl.
First run
Note: if you installed via Homebrew or go install, replace ./sonosh with sonosh.
Discover speakers:
./sonosh discover
./sonosh discover --format json
./sonosh discover --all # include invisible/bonded devices (advanced)
Show room status:
./sonosh status --name "Kitchen"
./sonosh now --name "Kitchen"
./sonosh status --name "Kitchen" --format json
Playback controls:
./sonosh play --name "Kitchen"
./sonosh pause --name "Kitchen"
./sonosh stop --name "Kitchen"
./sonosh next --name "Kitchen"
./sonosh prev --name "Kitchen"
Watch live events:
./sonosh watch --name "Kitchen"
./sonosh watch --name "Kitchen" --format json
./sonosh watch --name "Kitchen" --format tsv
Note: this starts a local callback server for UPnP events; your OS firewall may prompt to allow incoming connections.
CLI overview
Run sonosh --help for the full list. Most commonly used:
- Discovery & status:
discover,status/now,watch - Playback controls:
play,pause,stop,next,prev,open,enqueue,play-url,play-uri,linein,tv - Grouping:
group status,group join,group unjoin,group solo,group party,group dissolve - Queue:
queue list,queue play,queue remove,queue clear - Favorites:
favorites list,favorites open - Scenes:
scene save,scene apply,scene list,scene delete - Spotify search:
smapi search(recommended), optionalsearch spotify(Spotify Web API)
Queue management
List the queue:
./sonosh queue list --name "Kitchen"
./sonosh queue list --name "Kitchen" --format json
Play or remove a queue entry (positions are 1-based):
./sonosh queue play --name "Kitchen" 1
./sonosh queue remove --name "Kitchen" 3
Clear the queue:
./sonosh queue clear --name "Kitchen"
Scenes
Save the current room layout and volumes as a scene:
./sonosh scene save "Evening"
Restore a saved scene:
./sonosh scene apply "Evening"
Manage saved scenes:
./sonosh scene list
./sonosh scene delete "Evening"
Scenes are stored in your user config dir as sonoscli/scenes.json (e.g. ~/.config/sonoscli/scenes.json on macOS/Linux).
Favorites
List Sonos Favorites:
./sonosh favorites list --name "Kitchen"
./sonosh favorites list --name "Kitchen" --format json
Play a favorite by index:
./sonosh favorites open --name "Kitchen" --index 1
Or play by title:
./sonosh favorites open --name "Kitchen" "BBC Radio 6 Music"
Media URLs
Play a direct media URL:
./sonosh play-uri --name "Kitchen" "https://example.com/stream.mp3"
Play a URL through the Sonos-safe local proxy (requires ffmpeg; yt-dlp for YouTube and media pages):
./sonosh play-url --name "Kitchen" "https://www.youtube.com/watch?v=-n_rdQIVahw"
./sonosh play-url --name "Kitchen" "https://music.youtube.com/playlist?list=PL..."
./sonosh play-url --name "Kitchen" --playlist-limit 10 "https://music.youtube.com/playlist?list=PL..."
./sonosh play-url --name "Kitchen" "https://example.com/podcast/episode.mp3"
Play as radio (useful for live streams):
./sonosh play-uri --name "Kitchen" --radio --title "My Stream" "https://example.com/live.mp3"
Select line-in input:
./sonosh linein --name "Kitchen" --from "Living Room"
Select TV input (soundbar):
./sonosh tv --name "Living Room"
Room grouping
View current groups:
./sonosh group status
./sonosh group status --all # include invisible/bonded devices (advanced)
Move Bedroom into Living Room’s group:
./sonosh group join --name "Bedroom" --to "Living Room"
Room targeting supports fuzzy substring matching (and will suggest matches on ambiguity):
./sonosh group join --name "Off" --to "Bar" # "Office" joins "Bar"
./sonosh group join --name "Bed" --to "Liv" # "Bedroom" joins "Living Room"
Split a speaker out of its group:
./sonosh group unjoin --name "Bedroom"
Solo a speaker (ungroup its current group so it plays alone):
./sonosh group solo --name "Office"
Party mode (join all visible speakers to a target group):
./sonosh group party --to "Bar"
Remove grouping from the whole group:
./sonosh group dissolve --name "Living Room"
Keep Office playing on its own:
./sonosh group solo --name "Office"
./sonosh open --name "Office" "https://open.spotify.com/album/<id>"
Add a speaker back to a group:
./sonosh group join --name "Office" --to "Bar"
Control group volume and mute:
./sonosh group volume get --name "Living Room"
./sonosh group volume set --name "Living Room" 25
./sonosh group mute get --name "Living Room"
./sonosh group mute toggle --name "Living Room"
Control room volume and mute:
./sonosh volume get --name "Kitchen"
./sonosh volume set --name "Kitchen" 25
./sonosh mute get --name "Kitchen"
./sonosh mute toggle --name "Kitchen"
Room targeting
Target a speaker by:
--name "Kitchen"(Sonos room name)--ip 192.168.0.250(speaker IP)
Most commands must be sent to the group coordinator (the device that owns transport state for the group). sonosh resolves the coordinator automatically so commands behave like the Sonos app.
Spotify search
Search via Sonos (SMAPI; no Spotify Web API credentials):
./sonosh smapi services
./sonosh smapi categories --service "Spotify"
./sonosh smapi browse --service "Spotify" --id root
./sonosh auth smapi begin --service "Spotify"
# open the printed URL in a browser, link your account, then:
./sonosh auth smapi complete --service "Spotify" --code <linkCode> --wait 5m
./sonosh smapi search --service "Spotify" --category tracks "gareth emery"
./sonosh smapi search --service "Spotify" --category tracks --open --name "Office" "gareth emery"
Some AppLink services only return a native app authentication URL and no device-link code. In that case, auth smapi begin prints the app URL and notes that sonosh cannot complete token storage automatically.
Play from a search query (shortcut for SMAPI search + open):
./sonosh play spotify --name "Office" "gareth emery"
./sonosh play spotify --name "Office" --category albums "gareth emery"
SMAPI tokens are stored under your user config dir as sonoscli/smapi_tokens.json (e.g. ~/.config/sonoscli/smapi_tokens.json on macOS/Linux).
Search via Spotify Web API (prints playable URIs):
export SPOTIFY_CLIENT_ID="..."
export SPOTIFY_CLIENT_SECRET="..."
./sonosh search spotify --type track --limit 5 "daft punk harder better"
./sonosh search spotify --type playlist "focus"
Open the first result on Sonos:
./sonosh search spotify --open --name "Kitchen" "miles davis so what"
Enqueue and play:
./sonosh open --name "Kitchen" spotify:track:6NmXV4o6bmp704aPGyTVVG
./sonosh open --name "Kitchen" https://open.spotify.com/track/6NmXV4o6bmp704aPGyTVVG
Enqueue only:
./sonosh enqueue --name "Kitchen" spotify:playlist:37i9dQZF1DXcBWIGoYBM5M
Notes:
- The enqueue implementation tries Spotify Sonos service numbers
2311and3079for compatibility. - Use
--titleto override the queue display title for some entries.
Development
Makefile
make fmt
make test
make build
make lint
make lint requires golangci-lint:
brew install golangci-lint
# or:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
pnpm helper scripts
This repo includes a minimal package.json so you can drive the workflow with pnpm (no Node dependencies required):
pnpm build
pnpm test
pnpm format
pnpm lint
pnpm sonosh -- discover
pnpm sonosh -- status --name "Kitchen"
pnpm sonosh -- open --name "Kitchen" spotify:track:6NmXV4o6bmp704aPGyTVVG
pnpm sonosh -- search spotify "miles davis so what"
pnpm sonosh -- group status
pnpm sonosh -- queue list --name "Kitchen"
CI runs: gofmt check, go vet, go test, and golangci-lint.
Global flags
--ip <ip>: target by IP--name <name>: target by speaker name (defaults tosonosh config defaultRoomif set)--timeout <duration>: discovery/network timeout (default15s, configurable withsonosh config set defaultTimeout 10s)--format plain|json|tsv: output format (defaults tosonosh config formatif set)--json: deprecated alias for--format json--debug: enable detailed trace logs (SSDP/topology/SOAP timings)
Configuration
Persist small local defaults so repeated commands stay terse:
./sonosh config get
./sonosh config set defaultRoom "Office"
./sonosh config set defaultTimeout 10s
./sonosh config set format json
./sonosh config unset defaultRoom
Supported keys:
defaultRoom: room used when--name/--ipis omitted.defaultTimeout: discovery/network timeout used when--timeoutis omitted. The built-in default is15s.format: default output format (plain,json, ortsv).
Snake-case aliases (default_room, default_timeout) are accepted too.
Troubleshooting
discoveris empty:- Some networks block multicast/SSDP;
sonoshfalls back to scanning local /24 subnets for port1400and then uses Sonos topology to list all rooms. - Ensure Wi‑Fi client isolation is off and you’re on the same LAN/subnet.
- Some networks block multicast/SSDP;
- Discovery is slow or flaky:
- The default timeout is
15s. Usesonosh config set defaultTimeout 20sto make a longer timeout sticky, or pass--timeout 5sin scripts that should fail fast. - Run
sonosh --debug discoverto see whether SSDP multicast is timing out and whether topology calls are slow.
- The default timeout is
- Discovery / SOAP calls hang or time out on your network:
sonoshretries local Sonos HTTP/SOAP calls viacurlas a workaround for some network/firmware quirks.
- Commands fail with UPnP/SOAP errors:
- Verify you can reach
http://<speaker-ip>:1400/from this machine. - Try targeting by
--name(it resolves the coordinator).
- Verify you can reach
- Spotify enqueue fails:
- Confirm Spotify is linked and playable in the Sonos app.
- Some systems behave differently per firmware/service configuration.
Inspiration / references
This project was informed by the Sonos control ecosystem and the SoCo Python library:
https://github.com/SoCo/SoCo
Design doc
See docs/spec.md.
License
MIT License. See LICENSE.