A scriptable replacement for the audio-device half of macOS Audio MIDI Setup — built for humans at a terminal and for AI agents driving it as a tool. Everything the GUI does for audio devices, as composable commands with stable JSON output:
- list / inspect devices, switch default input/output/system device
- volume and mute, per-device and per-channel
- sample-rate control (no CLI tool did this before)
- full aggregate-device lifecycle: create, destroy, add/remove sub-devices, pick the clock device, toggle per-sub-device drift compensation
- multi-output devices with Audio-MIDI-Setup-style drift defaults
Requires macOS 13+. No dependencies beyond the system CoreAudio framework.
curl -fsSL https://dl.agora.build/audictl/install.sh | bash
# or:
npm install -g @agora-build/audictlOr build from source:
swift build -c release
cp .build/release/audictl /usr/local/bin/audictl list # table of all devices
audictl list --aggregate # aggregates with members inline:
# 58 Aggregate Device 18 18 ...
# └ BlackHole 2ch [clock]
# └ BlackHole 16ch drift
audictl list --input # only devices with input channels
audictl info scarlett # fuzzy name matching everywhere
audictl default get output
audictl default set output "MacBook Pro Speakers"
audictl volume set speakers 40 # 0-100 percent (or 0.0-1.0 with a decimal point)
audictl mute toggle minifuse --scope input
audictl rate get minifuse
audictl rate set minifuse 96k # validates against supported rates,
# waits for the device to settle
audictl aggregate # compositions of all aggregates (alias: aggregate show)
audictl aggregate show "Studio Rig" # one composition: sub-devices, clock, drift
audictl aggregate create --name "Studio Rig" --devices "minifuse,BlackHole 2ch" --clock minifuse
audictl aggregate add "Studio Rig" "BlackHole 16ch" --drift
audictl aggregate set-clock "Studio Rig" "BlackHole 2ch"
audictl aggregate drift "Studio Rig" "BlackHole 16ch" off
audictl aggregate remove "Studio Rig" "BlackHole 16ch"
audictl aggregate destroy "Studio Rig"
audictl multi create --name "Everywhere" --devices "speakers,office hdmi"Devices are addressed by UID, numeric ID, exact name, or unique name
substring — see SCHEMA.md for resolution order and the --by-uid /
--by-id / --by-name overrides.
list always prints one row per device (the Audio MIDI Setup sidebar);
aggregate show prints what's inside (the "Use" checkboxes) — bare
audictl aggregate does the same for all of them. A multi-output device is a
stacked aggregate — same CoreAudio
class, outputs mirrored instead of channels concatenated — so every
aggregate command (show, add, remove, set-clock, drift) works on
it; Audio MIDI Setup's "Primary Device" is the same field audictl shows as
[clock]. Sub-devices keep their friendly names even while the hardware is
unplugged, matching what the GUI displays.
Add --json to any command for a stable envelope on stdout:
$ audictl default set output speakers --json
{"changed":false,"data":{"device":{...},"role":"output"},"ok":true,"schemaVersion":1}The contract (SCHEMA.md):
ok+changed: mutations are idempotent — re-running a command that is already satisfied returnsok: true, changed: false, exit 0.- Mutating commands return the resulting state; no follow-up read needed.
- Machine-readable errors with typed codes and structured details — an
AMBIGUOUS_DEVICEerror lists the candidates so a retry can pin a UID. - Exit codes distinguish not-found (2), ambiguous (3), unsupported (4), CoreAudio errors (5), timeouts (6).
- Device UIDs are durable; numeric ids are session-scoped. Store UIDs.
--quiet suppresses output entirely (exit code only); --timeout <s> bounds
the wait for asynchronous device operations (creation, rate changes).
swift test # unit + CLI parsing (mocked HAL)
AUDICTL_INTEGRATION=1 swift test --filter IntegrationTests # real CoreAudioIntegration tests build their aggregates as private devices (visible only to the test process), so they never pollute the machine's device list.
- MIDI Studio features (CoreMIDI) — out of scope for v1
audictl mcp(MCP server mode) — planned; the core library is already separated from the CLI for it- Homebrew formula
Push a v* tag. CI builds arm64 + x86_64 binaries, runs the test suites,
creates a GitHub Release, publishes @agora-build/audictl to npm, and mirrors
the tarballs plus install.sh to dl.agora.build/audictl/.