# Bleswiftcli Macos Bluetooth

> A macOS command-line tool for Bluetooth Low Energy operations—scan, connect, pair, read, write, inspect GATT, and L2CAP channels.

- Skill: `aradotso/bleswiftcli-macos-bluetooth` (Agent Skill)
- Install (CLI): `npx skillmds@latest add aradotso/bleswiftcli-macos-bluetooth`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aradotso/bleswiftcli-macos-bluetooth/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aradotso (https://skillmd.com/u/aradotso)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aradotso/bleswiftcli-macos-bluetooth

---


# BLESwiftCLI — macOS Bluetooth LE Command-Line Tool

> Skill by [ara.so](https://ara.so) — Devtools Skills collection.

`ble` is a macOS command-line tool for Bluetooth Low Energy operations: scanning, connecting, pairing, reading/writing characteristics and descriptors, inspecting GATT databases, and opening L2CAP channels. Built on BLESwift and swift-argument-parser.

## Installation

### Homebrew

```sh
brew install kylebrowning/tap/ble
```

### Mint

```sh
mint install kylebrowning/BLESwiftCLI
```

### From Source

```sh
swift build -c release
cp .build/release/ble /usr/local/bin/
```

**Permissions**: On first run, macOS prompts for Bluetooth access. Grant it to your terminal app (Terminal, iTerm, etc.) in *System Settings → Privacy & Security → Bluetooth*.

## Core Concepts

- **Peripherals** are identified by UUID or name substring
- **Services** and **characteristics** use 16-bit SIG UUIDs (e.g. `180F`, `2A19`) or full 128-bit UUIDs
- Connections are held open only while the CLI process runs
- Pairing is triggered by accessing encrypted characteristics—no explicit pair API
- Output: data (scan results, values, JSON) → **stdout**; status/progress → **stderr**

## Commands

### Scanning for Devices

Scan for all nearby BLE devices:

```sh
ble scan
```

Scan for specific service (e.g. Heart Rate `180D`) with unlimited time:

```sh
ble scan -s 180D --timeout 0
```

Live RSSI updates with duplicate advertisements:

```sh
ble scan --allow-duplicates
```

Filter by minimum signal strength:

```sh
ble scan --min-rssi -70
```

JSON output for scripting:

```sh
ble scan --json | jq '.name, .rssi, .uuid'
```

**Output format** (interactive): Live table sorted by signal strength, showing name, RSSI (color-coded green/yellow/red), peripheral UUID, advertised services, manufacturer data.

**Output format** (piped/JSON): One line per advertisement event.

### Connecting to a Peripheral

Connect by UUID:

```sh
ble connect 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
```

Connect by name substring (resolves via scan):

```sh
ble connect "Kyle's Sensor"
```

Connect and auto-reconnect on disconnect:

```sh
ble connect mydevice -s 180D --reconnect
```

The connection stays open until Ctrl-C or the process exits. Lifecycle events stream to stderr.

### Pairing with a Device

CoreBluetooth has no explicit pair API. Pairing is triggered by accessing a characteristic that requires encryption:

```sh
ble pair mydevice -s <service-uuid> -c <protected-characteristic-uuid>
```

To trigger pairing via write:

```sh
ble pair mydevice -s FFF0 -c FFF1 --write 0x01
```

This connects, attempts the read/write, and macOS shows the pairing dialog. Approve it to bond the device.

**Unpair**: *System Settings → Bluetooth*, click info button on device, select "Forget This Device".

### Inspecting GATT Database

Enumerate all services, characteristics, and descriptors:

```sh
ble inspect mydevice
```

Include current values of readable characteristics:

```sh
ble inspect mydevice --read
```

JSON output:

```sh
ble inspect mydevice --json | jq
```

**Example output**:

```
Service 180F — Battery
  2A19 — Battery Level  [read, notify] = 0x5A (1 byte, uint 90, "Z")
    Descriptor 2902 — Client Characteristic Configuration
```

### Reading Characteristics

One-time read:

```sh
ble read mydevice -s 180F -c 2A19
```

Subscribe to notifications:

```sh
ble read mydevice -s 180F -c 2A19 --notify
```

Read specific number of notifications:

```sh
ble read mydevice -s 180F -c 2A19 --notify --count 5
```

Read a descriptor:

```sh
ble read mydevice -s 180F -c 2A19 -d 2901
```

**Output**: Hex representation plus interpretations (byte count, little-endian uint, UTF-8 string).

### Writing Characteristics

Write hex value:

```sh
ble write mydevice -s FFF0 -c FFF1 --hex 0x01FF
```

Write string:

```sh
ble write mydevice -s FFF0 -c FFF1 --string "hello"
```

Write from payload file (YAML/JSON):

```sh
ble write mydevice -p command.yaml
```

Write and wait for notification on another characteristic:

```sh
ble write mydevice -p command.yaml --expect-reply-on FFF2
```

Dry-run (print encoded bytes without writing):

```sh
ble write mydevice -p command.yaml --dry-run
```

Write a descriptor:

```sh
ble write mydevice -s FFF0 -c FFF1 -d 2901 --string "label"
```

The tool automatically checks characteristic properties and chooses write-with-response or write-without-response. Use `--without-response` to force it.

### Payload Files

Structured payloads in YAML or JSON. Fields are encoded in order and concatenated.

**Example** (`command.yaml`):

```yaml
service: 180F
characteristic: 2A19
writeType: withResponse  # optional: withResponse | withoutResponse
fields:
  - { type: u8,     value: 1 }
  - { type: u16le,  value: 5000 }
  - { type: i32be,  value: -70 }
  - { type: string, value: "hello" }
  - { type: hex,    value: "DEADBEEF" }
  - { type: pad,    length: 2 }
```

**Supported types**:
- Integers: `u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`
- Endianness: `u16le` (little-endian, default), `u16be` (big-endian)
- `string`: UTF-8 encoded
- `hex`: raw hex bytes
- `pad`: zero-fill N bytes

Command-line flags (`-s`, `-c`) override the file's `service`/`characteristic`.

### L2CAP Channels

Open a connection-oriented L2CAP channel:

```sh
ble l2cap mydevice --psm 0x0080
```

Send hex data once, then stream incoming:

```sh
ble l2cap mydevice --psm 128 --send-hex 0x01FF
```

Raw binary output (for piping):

```sh
ble l2cap mydevice --psm 128 --raw > capture.bin
```

The channel stays open until Ctrl-C or the peripheral closes it.

## Common Patterns

### Find and Connect to a Specific Device Type

```sh
# Scan for heart-rate monitors
ble scan -s 180D --timeout 5

# Connect to one by name
ble connect "Polar H10" --reconnect
```

### Read Battery Level

```sh
ble read mydevice -s 180F -c 2A19
```

### Monitor Heart Rate Notifications

```sh
ble read mydevice -s 180D -c 2A37 --notify
```

### Send Multi-Field Command

Create `command.yaml`:

```yaml
service: FFF0
characteristic: FFF1
fields:
  - { type: u8, value: 0x02 }       # Command ID
  - { type: u16le, value: 1000 }    # Parameter
```

Execute:

```sh
ble write mydevice -p command.yaml
```

### Automated Testing / Scripting

```sh
#!/bin/bash
# Scan for devices, filter by service, connect to first match
DEVICE=$(ble scan -s 180D --timeout 5 --json | jq -r '.uuid' | head -n1)
ble connect "$DEVICE" &
sleep 2
ble read "$DEVICE" -s 180D -c 2A37 --notify --count 10
```

### Full GATT Inspection

```sh
# Human-readable with values
ble inspect mydevice --read

# JSON for parsing
ble inspect mydevice --json | jq '.services[].characteristics[].uuid'
```

### Pair and Then Write to Protected Characteristic

```sh
# Trigger pairing
ble pair mydevice -s FFF0 -c FFF1 --write 0x00

# Subsequent writes succeed without re-pairing
ble write mydevice -s FFF0 -c FFF1 --hex 0x0102
```

## Troubleshooting

### "Bluetooth access denied"

Grant Bluetooth permission to your terminal app in *System Settings → Privacy & Security → Bluetooth*.

### "Peripheral not found"

- Ensure the device is powered on and in range
- Try `ble scan` first to confirm it's advertising
- Use the exact UUID or a unique name substring

### "Characteristic does not support write"

The characteristic's properties don't include `write` or `writeWithoutResponse`. Use `ble inspect` to confirm properties.

### "Value exceeds MTU"

The payload is larger than the negotiated maximum write length. Split the data or reduce the payload size. The tool warns about this but doesn't auto-split.

### "Connection timeout"

- Device may be out of range or turned off
- macOS Bluetooth stack may be busy—try toggling Bluetooth off/on in System Settings
- Some devices bond to one host at a time—unpair from other devices

### Pairing dialog doesn't appear

The characteristic isn't marked as requiring encryption. Check the device's GATT specification or try a different characteristic known to be protected.

### No notifications received

- Verify the characteristic supports notify/indicate: `ble inspect mydevice`
- Some devices require enabling notifications via the Client Characteristic Configuration Descriptor (CCCD `2902`)—the tool does this automatically

## Integration with Other Tools

### Pipe to `jq` for JSON filtering

```sh
ble scan --json | jq 'select(.rssi > -60) | {name, uuid, rssi}'
```

### Capture raw L2CAP stream

```sh
ble l2cap mydevice --psm 128 --raw | xxd
```

### Log notifications to file

```sh
ble read mydevice -s 180D -c 2A37 --notify > heart_rate.log
```

### Use in scripts with error handling

```sh
#!/bin/bash
set -e
DEVICE="MyDevice"
if ! ble connect "$DEVICE" --timeout 5 2>&1 | grep -q "Connected"; then
  echo "Failed to connect"
  exit 1
fi
ble write "$DEVICE" -p command.yaml
```

## Environment and Configuration

- **NO_COLOR**: Set to disable colored output
- Piping automatically disables colors and switches to line-by-line output
- Timeout defaults: scan 10s, connect 30s (override with `--timeout`)

## Development and Testing

Build from source:

```sh
git clone https://github.com/kylebrowning/BLESwiftCLI.git
cd BLESwiftCLI
swift build
.build/debug/ble --help
```

Run tests (no hardware required):

```sh
swift test
```

Tests use BLESwift's `FakeCentral` and `FakePeripheral` for logic validation.

## Reference

- **Standard BLE Services**: [Bluetooth SIG GATT Services](https://www.bluetooth.com/specifications/gatt/services/)
  - `180F` = Battery Service
  - `180D` = Heart Rate
  - `180A` = Device Information
- **Standard Characteristics**: [Bluetooth SIG GATT Characteristics](https://www.bluetooth.com/specifications/gatt/characteristics/)
  - `2A19` = Battery Level
  - `2A37` = Heart Rate Measurement
  - `2902` = Client Characteristic Configuration (CCCD)

---

**Key takeaway**: Use `ble scan` to discover, `ble inspect` to explore GATT, `ble read`/`write` for data, and payload files for complex structured writes.

