macOS CLI that controls Apple Music playback and routes audio to HomePods.
- macOS with the Music app
osascript(built-in)shortcuts(built-in, optional for thenativebackend)- Go toolchain to build (if building from source)
On first use, macOS may prompt you to allow your terminal (or the built binary) to control:
- Music (via Apple Events)
- Shortcuts (if you use the
nativebackend)
--backend airplay: selects Music.app AirPlay output device(s) and plays a playlist (the Mac is the sender).--backend native: runs a Shortcuts automation you map inconfig.json(can be set up so HomePod plays natively).
- “Rooms” = AirPlay device names as seen by Music.app (HomePods, Apple TVs, speakers, etc).
out setchanges Music.app’s current AirPlay outputs (it does not edit your config).playplays a Music.app user playlist (by fuzzy search or by ID).config.jsonis only for defaults and aliases (so you don’t have to type--roomevery time).
List available AirPlay outputs (these names are what you pass as “rooms”):
homepodctl devicesPick outputs to play through (sets Music.app’s current outputs):
homepodctl out set "Bedroom"Play a playlist by fuzzy query:
homepodctl play chillIf the playlist name has spaces, quote it:
homepodctl play "Songs I've been obsessed recently pt. 2"If multiple playlists match, auto-picks the best match; to pick interactively:
homepodctl play autumn --chooseSee what’s playing (track/album/playlist + outputs):
homepodctl statusShortcut for status:
homepodctl nowWatch changes:
homepodctl status --watch 1sSearch playlists (for IDs / debugging):
homepodctl playlists --query chillIf a playlist name is ambiguous or tricky to match (emoji/whitespace), use IDs:
homepodctl playlists --query autumn
homepodctl play --playlist-id <PERSISTENT_ID>Set volume (if rooms are omitted, uses defaults.rooms; if that’s empty, uses the currently selected outputs in Music.app):
homepodctl vol 50
homepodctl volume 35 "Living Room"Create a starter config:
homepodctl config-initThis writes config.json under your macOS user config dir (typically ~/Library/Application Support/homepodctl/config.json).
Defaults are used when flags are omitted. For example, if you set:
defaults.backend = "airplay"defaults.rooms = ["Bedroom"]
…then you can just run:
homepodctl play chillList configured aliases:
homepodctl aliasesRun an alias from your config:
homepodctl run bed-exampleEdit config.json, map room -> playlist -> shortcut name, and run:
homepodctl play --backend native --room "Bedroom" --playlist "Example Playlist"CLI help:
homepodctl --help
homepodctl --verbose status
homepodctl help playVerbose diagnostics can also be enabled via HOMEPODCTL_VERBOSE=1.
Run built-in diagnostics:
homepodctl doctor
homepodctl doctor --jsonGenerate shell completions:
homepodctl completion zsh
homepodctl completion bash
homepodctl completion fishInspect and update config values:
homepodctl config validate --json
homepodctl config get defaults.backend
homepodctl config set defaults.backend airplay
homepodctl config set defaults.rooms "Bedroom" "Living Room"Dry-run mutating commands without side effects:
homepodctl play chill --dry-run --json
homepodctl out set "Bedroom" --dry-run --json
homepodctl volume 30 --dry-run --json
homepodctl run bed --dry-run --json0: success2: usage/flag/validation error3: config or automation validation error4: backend command error (osascript/shortcuts)1: other runtime failures
homepodctl devices/homepodctl out list: list AirPlay deviceshomepodctl out set <room> ... [--json|--plain|--dry-run]: select Music.app outputshomepodctl play <query> [--json|--plain|--dry-run]/homepodctl play --playlist-id <id>: play a playlisthomepodctl playlists --query <text> [--json|--plain]: search playlistshomepodctl status [--json|--plain]/homepodctl now/homepodctl status --watch 1s: now playinghomepodctl pause|stop|next|prev [--json|--plain]: transport controlshomepodctl volume <0-100> [room ...] [--json|--plain|--dry-run]/homepodctl vol ...: output volumehomepodctl aliases [--json|--plain]/homepodctl run <alias> [--json|--plain|--dry-run]: config shortcutshomepodctl native-run --shortcut <name> [--json|--dry-run]: run a Shortcut directlyhomepodctl config validate|get|set ...: validate and edit config values (defaults.*)homepodctl config-init: create starter confighomepodctl doctor: diagnostics checklisthomepodctl completion <bash|zsh|fish>: generate completion scripthomepodctl automation validate|plan|run|init ...: routine workflows (non-interactive by default; add--dry-runto preview)homepodctl version: version info
- You built it but it still behaves “old”: if you run
make build, the binary is./homepodctl. Runninghomepodctl ...might be a different binary on your PATH. - Rooms are not flags: use
--room "Bedroom"(repeatable), not--bedroom/--Bedroom. out setdoesn’t edit config: it only changes Music.app’s current outputs. Useconfig-init+ editdefaults.roomsif you want persistent defaults.
Automation commands are being designed for routine playback flows and agent usage.
Design docs:
- CLI spec:
docs/automation-v1-cli-spec.md - User quickstart:
docs/automation/quickstart-user.md - Agent quickstart:
docs/automation/quickstart-agent.md - Preset templates:
docs/automation/presets/
Canonical presets included:
docs/automation/presets/morning.yamldocs/automation/presets/focus.yamldocs/automation/presets/winddown.yamldocs/automation/presets/party.yamldocs/automation/presets/reset.yaml
This tool is macOS-only (it relies on osascript + Music.app, and optionally shortcuts).
- Homebrew (recommended):
brew tap agisilaos/tapbrew install homepodctl
- From source (recommended while iterating):
make build - Release preflight (recommended):
make release-check VERSION=vX.Y.Zvalidates changelog/test/vet and produces a version-stamped local binary. - Prebuilt binaries:
make release VERSION=vX.Y.Zpublishes a GitHub Release and updates the Homebrew formula inagisilaos/homebrew-tap. go install(after publishing):go install github.com/agisilaos/homepodctl/cmd/homepodctl@latest
This project is not affiliated with Apple.