blueutil drives Bluetooth on macOS from the command line
CLI for bluetooth on OSX: power, discoverable state, list, inquire devices, connect, info, …
At a glance
- What is it?
- blueutil is an Objective-C command line tool that queries and controls Bluetooth on macOS through the IOBluetooth framework, covering power, discoverability, paired devices, connection state, pairing and waiting on RSSI. Its most useful feature for scripting is the output format switch, which turns human readable text into JSON.
- Who is it for?
- Adopt blueutil if you script macOS Bluetooth, because there is no supported command line for it and this tool covers the states and events you would otherwise click through System Settings. Do not adopt it if you need a stable public API, since it calls private framework functions and the README says so, which means it can break with a macOS update.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 40 days ago.
- What is it written in?
- Mainly Objective-C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
It is built on private framework APIs
The notes section says it plainly: blueutil uses private API from the IOBluetooth framework, for example the `IOBluetoothPreference*()` functions. There is no public command line for managing Bluetooth on macOS, so a tool like this either calls the private interfaces or shells out to the preference pane, and blueutil does the former. The consequence is a maintenance contract you have to accept rather than ignore. The same notes describe a specific side effect of the private route: opening the Bluetooth preference pane always turns on discoverability if Bluetooth power is on, or if it is switched on while the preference pane is open, and that change of state is not reported by the function blueutil uses. That is the kind of detail you only learn from someone who has read the framework headers carefully.
A second path exists through system_profiler
There is an alternative method, and it exists for a concrete reason. By default blueutil uses the IOBluetooth framework APIs to query paired devices, but you can set the `BLUEUTIL_USE_SYSTEM_PROFILER=1` environment variable to use the `system_profiler` command instead. The reason is a bug in the private path: some multi-point Bluetooth devices, which can connect to multiple devices simultaneously, may not report their connection status correctly through the IOBluetooth APIs but do show the correct status via system_profiler. The README marks the system_profiler method as experimental and warns that it may have compatibility issues with some blueutil commands. The environment variable can be set for a single command or exported for the whole shell, and the examples show all three forms.
The command set covers state, devices and waiting
The usage block is the real reference. Power and discoverable each have a short and a long form and work as both a query and a setter, with the state argument being one of 1, on, 0, off or toggle. There are list commands for paired, connected and recent devices, an inquiry command that runs a device discovery for ten seconds by default excluding the time for name updates, and information and connection state commands that take a device ID. Pairing, unpairing, connecting and disconnecting are there too, and the wait commands are the part scripts want: `--wait-connect ID` and `--wait-disconnect ID` with a timeout, and `--wait-rssi ID OP VALUE` with a period and a timeout, where the operation can be a comparison operator or its word equivalent and the RSSI value is zero for the golden range and negative 129 if it cannot be read. The pair command takes an optional PIN of up to sixteen characters, which is used instead of interactive input in the specific pair mode.
Output formats are what make it scriptable
The format switch is the feature that turns a utility into something you can build on. The `--format FORMAT` option changes the output format of the info and all listing commands, with four choices. `default` is human readable text output not intended for consumption by scripts. `new-default` is human readable comma separated key-value pairs, marked experimental with the behaviour liable to change. `json` is compact JSON and `json-pretty` is pretty printed JSON. The device ID accepted by the commands is equally flexible, being either a Bluetooth address in the plain twelve digit form or the dashed or colon separated forms, or the name of a device to search for in the paired or recent devices. So you can drive it from a script that knows names rather than addresses, and parse the result as JSON.
macOS 12 removed several things
Several capabilities quietly disappeared in macOS 12 Monterey, and the documentation is precise about which. The favourites list returns an empty list starting with macOS 12, the recent devices list does the same, and the add and remove favourite commands do nothing. Favourite devices and the recent access date are not stored starting with macOS 12, because the framework returns the current time for the recent access date instead. The unpair command, the wait commands and the new-default format are all marked experimental in the usage text. This is the practical boundary of the tool, and it is the first thing to check when a script that worked on an older machine stops behaving on a newer one.
It refuses to run as root, and the exit codes are documented
There is a safety check that will stop you if you run the tool with sudo. Due to possible problems, blueutil will refuse to run as root user, and the environment variable `BLUEUTIL_ALLOW_ROOT=1` overrides it, with the example given as `sudo BLUEUTIL_ALLOW_ROOT=1 blueutil`. The exit codes are documented too, which is unusual and useful: 0 for success, 1 for a general failure, 64 for wrong usage such as missing or unexpected arguments, 69 for Bluetooth or interface not available, 70 for an internal error, 71 for a system error like a shortage of memory, 75 for a timeout error, and 134 for an abort signal that may indicate absence of access to the Bluetooth API. Those specific codes are what you branch on in a script.
Building it is a make and a few scripts
The Makefile is small and shows the shape of the project, which is a single Objective-C source file. The compile flags are `-Wall -Wextra -Werror` with a minimum macOS version of 10.9 and the Foundation and IOBluetooth frameworks linked. The targets are build, format with clang-format, update_usage which regenerates the usage text embedded in the binary, test which runs the test script, clean, install and uninstall, with the install prefix defaulting to `/usr/local`:
# build
make
# install/update
make installThe repository root holds `blueutil.m`, the Xcode project, a prefix header, the `release`, `test`, `update_usage` and `verify_release` scripts, and a `CHANGELOG.md`. The development section shows the workflow: `make build update_usage` to refresh the usage text, `make format` for clang-format, `make test` for the tests, and the release scripts for tagging and publishing. The licence is a custom one GitHub cannot classify, and the copyright is a handover, originally written by Frederik Seiffert and copyright since 2011 by Ivan Kuchin. The latest release is v2.14.0 from 2026-09-09.
Editorial conclusion
Adopt blueutil if you script macOS Bluetooth, because there is no supported command line for it and this tool covers the states and events you would otherwise click through System Settings. Do not adopt it if you need a stable public API, since it calls private framework functions and the README says so, which means it can break with a macOS update. Verify first which macOS version you are on, since several features were removed in macOS 12 Monterey, and read the root refusal behaviour, because the tool declines to run as root unless you override it.
Frequently asked questions
How do I install blueutil on macOS?
With Homebrew using `brew install blueutil`, with MacPorts using `port install blueutil`, or from source by cloning the repository and running `make` followed by `make install`. The Homebrew and MacPorts routes handle update and uninstall with the matching commands.
Why does blueutil use system_profiler instead of the IOBluetooth API?
Some multi-point Bluetooth devices, which connect to several devices at once, may not report their connection status correctly through the IOBluetooth APIs but do show the correct status via system_profiler. Set the environment variable BLUEUTIL_USE_SYSTEM_PROFILER=1 to switch, though the README marks that method as experimental.
How do I get JSON output from blueutil?
Pass `--format json` for compact JSON or `--format json-pretty` for pretty printed JSON. The default format is human readable text and is explicitly not intended for consumption by scripts.
What exit codes does blueutil use?
0 for success, 1 for a general failure, 64 for wrong usage, 69 for Bluetooth or interface not available, 70 for an internal error, 71 for a system error such as a shortage of memory, 75 for a timeout, and 134 for an abort signal that may indicate absence of access to the Bluetooth API.
Does blueutil run as root?
No, it refuses to run as root because of possible problems, and you can override that with the environment variable BLUEUTIL_ALLOW_ROOT=1, for example `sudo BLUEUTIL_ALLOW_ROOT=1 blueutil`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/toy-blueutil)Was this useful?