harald 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. harald-0.2.0/.github/workflows/ci.yml +30 -0
  2. harald-0.2.0/.gitignore +12 -0
  3. harald-0.2.0/CONTRIBUTING.md +96 -0
  4. harald-0.2.0/LICENSE +21 -0
  5. harald-0.2.0/PKG-INFO +283 -0
  6. harald-0.2.0/README.md +266 -0
  7. harald-0.2.0/assets/banner.svg +19 -0
  8. harald-0.2.0/devices/README.md +16 -0
  9. harald-0.2.0/devices/wh-1000xm5/device.toml +8 -0
  10. harald-0.2.0/devices/wh-1000xm5/driver.py +517 -0
  11. harald-0.2.0/devices/wh-1000xm5/protocol.md +127 -0
  12. harald-0.2.0/pyproject.toml +68 -0
  13. harald-0.2.0/scripts/make_banner.py +189 -0
  14. harald-0.2.0/src/harald/__init__.py +1 -0
  15. harald-0.2.0/src/harald/__main__.py +6 -0
  16. harald-0.2.0/src/harald/analyze.py +200 -0
  17. harald-0.2.0/src/harald/apk.py +490 -0
  18. harald-0.2.0/src/harald/arm.py +191 -0
  19. harald-0.2.0/src/harald/ble.py +194 -0
  20. harald-0.2.0/src/harald/capture/__init__.py +1 -0
  21. harald-0.2.0/src/harald/capture/att.py +144 -0
  22. harald-0.2.0/src/harald/capture/files.py +220 -0
  23. harald-0.2.0/src/harald/capture/l2cap.py +79 -0
  24. harald-0.2.0/src/harald/capture/rfcomm.py +220 -0
  25. harald-0.2.0/src/harald/capture/timeline.py +127 -0
  26. harald-0.2.0/src/harald/classic.py +500 -0
  27. harald-0.2.0/src/harald/classic_mac.py +569 -0
  28. harald-0.2.0/src/harald/cli.py +661 -0
  29. harald-0.2.0/src/harald/errors.py +35 -0
  30. harald-0.2.0/src/harald/library.py +145 -0
  31. harald-0.2.0/src/harald/playbook/__init__.py +51 -0
  32. harald-0.2.0/src/harald/playbook/claude-code/SKILL.md +272 -0
  33. harald-0.2.0/src/harald/playbook/codex/AGENTS.md +267 -0
  34. harald-0.2.0/src/harald/server.py +422 -0
  35. harald-0.2.0/src/harald/templates/driver.py.tmpl +155 -0
  36. harald-0.2.0/src/harald/templates/driver_classic.py.tmpl +384 -0
  37. harald-0.2.0/src/harald/templates/driver_cli.py.tmpl +29 -0
  38. harald-0.2.0/src/harald/templates/driver_payload.py.tmpl +106 -0
  39. harald-0.2.0/src/harald/templates/protocol.md.tmpl +22 -0
  40. harald-0.2.0/src/harald/templates/protocol_classic.md.tmpl +31 -0
  41. harald-0.2.0/src/harald/templates.py +383 -0
  42. harald-0.2.0/src/harald/workspace.py +49 -0
  43. harald-0.2.0/tests/capture_helpers.py +209 -0
  44. harald-0.2.0/tests/classic_fakes.py +144 -0
  45. harald-0.2.0/tests/conftest.py +23 -0
  46. harald-0.2.0/tests/fakes.py +174 -0
  47. harald-0.2.0/tests/fixtures/apk/jadx-output/resources/AndroidManifest.xml +4 -0
  48. harald-0.2.0/tests/fixtures/apk/jadx-output/sources/androidx/core/ble/GattCompat.java +20 -0
  49. harald-0.2.0/tests/fixtures/apk/jadx-output/sources/com/example/gadget/BleController.java +29 -0
  50. harald-0.2.0/tests/fixtures/apk/jadx-output/sources/com/example/gadget/Constants.java +17 -0
  51. harald-0.2.0/tests/fixtures/devices/demo-fan/device.toml +5 -0
  52. harald-0.2.0/tests/fixtures/devices/demo-fan/driver.py +1 -0
  53. harald-0.2.0/tests/fixtures/devices/demo-fan/protocol.md +3 -0
  54. harald-0.2.0/tests/fixtures/devices/demo-lamp/device.toml +8 -0
  55. harald-0.2.0/tests/fixtures/devices/demo-lamp/driver.py +1 -0
  56. harald-0.2.0/tests/fixtures/devices/demo-lamp/protocol.md +3 -0
  57. harald-0.2.0/tests/test_analyze.py +112 -0
  58. harald-0.2.0/tests/test_apk.py +381 -0
  59. harald-0.2.0/tests/test_arm.py +142 -0
  60. harald-0.2.0/tests/test_ble.py +181 -0
  61. harald-0.2.0/tests/test_capture_files.py +201 -0
  62. harald-0.2.0/tests/test_classic.py +449 -0
  63. harald-0.2.0/tests/test_classic_mac.py +750 -0
  64. harald-0.2.0/tests/test_classic_rules.py +129 -0
  65. harald-0.2.0/tests/test_cli.py +630 -0
  66. harald-0.2.0/tests/test_docs.py +316 -0
  67. harald-0.2.0/tests/test_library.py +200 -0
  68. harald-0.2.0/tests/test_packaging.py +36 -0
  69. harald-0.2.0/tests/test_playbook.py +231 -0
  70. harald-0.2.0/tests/test_rfcomm.py +235 -0
  71. harald-0.2.0/tests/test_server.py +498 -0
  72. harald-0.2.0/tests/test_templates.py +1072 -0
  73. harald-0.2.0/tests/test_timeline.py +448 -0
  74. harald-0.2.0/tests/test_version.py +18 -0
  75. harald-0.2.0/tests/test_wh1000xm5.py +409 -0
  76. harald-0.2.0/tests/test_workspace.py +20 -0
  77. harald-0.2.0/uv.lock +1294 -0
@@ -0,0 +1,30 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest, macos-latest]
17
+ python: ["3.13"]
18
+ include:
19
+ # the oldest python harald supports (requires-python in pyproject.toml)
20
+ - os: ubuntu-latest
21
+ python: "3.11"
22
+ runs-on: ${{ matrix.os }}
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: astral-sh/setup-uv@v6
26
+ with:
27
+ # pinned: uv.lock is written by this uv, and a newer uv may reject it
28
+ version: "0.12.9"
29
+ python-version: ${{ matrix.python }}
30
+ - run: uv sync --locked && uv run pytest -q && uv run ruff check
@@ -0,0 +1,12 @@
1
+ .superpowers/
2
+ docs/superpowers/
3
+ harald-work/
4
+ scripts/.fonts/
5
+ .venv/
6
+ __pycache__/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .DS_Store
@@ -0,0 +1,96 @@
1
+ # contributing
2
+
3
+ the most useful thing you can send is a device: a bluetooth le or classic gadget whose
4
+ protocol you worked out, so the next owner can control it without starting from
5
+ nothing. bug fixes and clearer docs are welcome too.
6
+
7
+ ## adding a device
8
+
9
+ each device is one folder, `devices/<slug>/`. the slug uses lowercase letters, digits
10
+ and hyphens, for example `demo-lamp`. the folder holds three files:
11
+
12
+ - `device.toml`: what the device is.
13
+ - `protocol.md`: the protocol, written up: service and characteristic uuids (the
14
+ rfcomm service uuid for a bluetooth classic gadget), the cipher if there is one,
15
+ and each command's payload.
16
+ - `driver.py`: a standalone driver with its own small cli. it must not import harald.
17
+
18
+ ```toml
19
+ name = "demo lamp"
20
+ vendor = "example co"
21
+ status = "decoded" # decoded, in progress or wanted
22
+ transport = "ble" # ble or classic; ble when left out
23
+ key_scope = "app-wide" # app-wide, or none when there is no key
24
+ key_hex = "00112233445566778899aabbccddeeff"
25
+ cipher = "aes-ecb" # aes-ecb or aes-cbc-zero-iv; leave out when there is none
26
+ service_uuids = ["0000ffe0-0000-1000-8000-00805f9b34fb"]
27
+ name_hint = "DEMO-LAMP" # optional: the start of the name the gadget shows
28
+ summary = "rgb lamp whose colour writes are encrypted with an app-wide key"
29
+ ```
30
+
31
+ a bluetooth classic gadget sets `transport = "classic"`, and its `service_uuids` holds
32
+ the vendor's rfcomm service uuid, the one `harald classic services` lists for its
33
+ control channel. set `name_hint` to the start of its paired name, without any serial
34
+ number:
35
+
36
+ ```toml
37
+ name = "demo speaker"
38
+ vendor = "example co"
39
+ status = "decoded"
40
+ transport = "classic"
41
+ key_scope = "none"
42
+ service_uuids = ["5d2f8a10-7c3e-4b9a-a6d1-0e4f2c8b9a37"]
43
+ name_hint = "DEMO-SPK"
44
+ summary = "speaker whose volume and eq are set over a vendor rfcomm channel"
45
+ ```
46
+
47
+ the playbook leaves your agent's work in `harald-work/<slug>/` (`protocol.md` and
48
+ `driver.py`); copy those over and add `device.toml`. before you open a pull request:
49
+
50
+ 1. set `ADDRESS_HINT` in `driver.py` to `""`, so it scans instead of using your own
51
+ unit's address. the scan looks for the service uuid, and for a name starting with
52
+ `NAME_HINT` when that is set, for gadgets that don't advertise their service. a
53
+ classic driver looks through the mac's paired devices instead: for a name starting
54
+ with `NAME_HINT`, else for the one device that lists the service uuid.
55
+ 2. from your checkout, run `uv run harald drive <slug> <command>` against the real
56
+ gadget, using values the vendor app's capture never contained. replaying captured
57
+ bytes does not count as decoded.
58
+ 3. run `uv run pytest -q`. a test loads every entry in `devices/`, so a broken entry,
59
+ a decoded entry without its `protocol.md` or `driver.py`, or a key that breaks the
60
+ keys rule fails ci. a `wanted` or `in progress` entry may be a `device.toml` alone.
61
+
62
+ ## the keys rule
63
+
64
+ some gadgets encrypt their commands. a `devices/` entry may carry a key only when it
65
+ is app-wide: identical on every unit, and shipped inside the vendor app that anyone
66
+ can download. set `key_scope = "app-wide"` for it.
67
+
68
+ never publish per-unit keys or pairing keys. that includes keys derived from one
69
+ unit's address or serial, keys created when you pair, and keys a vendor server hands
70
+ out. they stay in your own `harald-work/<slug>/`. harald refuses to load an entry
71
+ that has a key with any other scope, and reviewers check every entry by hand as well.
72
+
73
+ ## takedowns
74
+
75
+ every device lives entirely inside its own folder, and nothing else in harald depends
76
+ on it. if a vendor asks for an entry to be taken down, it is handled by removing that
77
+ one folder. vendors can open an issue naming the folder.
78
+
79
+ ## code changes
80
+
81
+ ```sh
82
+ uv sync
83
+ uv run pytest -q
84
+ uv run ruff check
85
+ ```
86
+
87
+ tests use fakes and recorded captures; ci never touches a radio. keep `src/harald/`
88
+ free of hints about any one device (a test checks), and keep every cli and mcp message
89
+ lowercase with no emojis.
90
+
91
+ ## commits
92
+
93
+ commits on `main` are authored as `k0nnect`, the maintainer. please write yours in the
94
+ same style: lowercase conventional messages such as `feat: add demo-lamp driver` or
95
+ `fix: keep generated driver help lowercase`, and no ai attribution lines, so no
96
+ `co-authored-by` trailers for tools.
harald-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 k0nnect
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
harald-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,283 @@
1
+ Metadata-Version: 2.5
2
+ Name: harald
3
+ Version: 0.2.0
4
+ Summary: helps coding agents work out the bluetooth le and classic protocol of gadgets you own
5
+ Project-URL: homepage, https://github.com/k0nnect/harald
6
+ Project-URL: source, https://github.com/k0nnect/harald
7
+ Project-URL: issues, https://github.com/k0nnect/harald/issues
8
+ Author: k0nnect
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: bleak>=3.0
13
+ Requires-Dist: cryptography>=44
14
+ Requires-Dist: mcp>=2.3
15
+ Requires-Dist: pyobjc-framework-iobluetooth>=11; sys_platform == 'darwin'
16
+ Description-Content-Type: text/markdown
17
+
18
+ <p align="center">
19
+ <img src="https://raw.githubusercontent.com/k0nnect/harald/main/assets/banner.svg" alt="harald ハーラル ᚼᛅᚱᛅᛚᛏᚱ. teach your agent to speak bluetooth." width="100%">
20
+ </p>
21
+
22
+ <p align="center">
23
+ <a href="https://pypi.org/project/harald/"><img alt="pypi" src="https://img.shields.io/pypi/v/harald?style=flat&label=pypi&labelColor=2b2b2b&color=c8102e"></a>
24
+ <a href="#四-agent-setup"><img alt="works with: claude code · codex · cursor" src="https://img.shields.io/badge/works%20with-claude%20code%20%C2%B7%20codex%20%C2%B7%20cursor-2b4bd8?style=flat&labelColor=2b2b2b"></a>
25
+ <a href="#三-devices"><img alt="devices: 1 decoded" src="https://img.shields.io/badge/devices-1%20decoded-4a4640?style=flat&labelColor=2b2b2b"></a>
26
+ <a href="https://github.com/k0nnect/harald/blob/main/LICENSE"><img alt="license: mit" src="https://img.shields.io/badge/license-mit-4a4640?style=flat&labelColor=2b2b2b"></a>
27
+ </p>
28
+
29
+ <p align="center">
30
+ <a href="#二-install">install</a> &nbsp;/&nbsp;
31
+ <a href="#一-how-it-works">how it works</a> &nbsp;/&nbsp;
32
+ <a href="#三-devices">devices</a> &nbsp;/&nbsp;
33
+ <a href="#五-bluetooth-classic">bluetooth classic</a> &nbsp;/&nbsp;
34
+ <a href="#七-contributing-a-device">add your device</a>
35
+ </p>
36
+
37
+ ![a claude code session uses harald to find a sony wh-1000xm5's control channel over bluetooth classic, is refused until a human arms the headphones, then turns noise cancelling on and reads the battery](https://raw.githubusercontent.com/k0nnect/harald/main/assets/demo.gif)
38
+
39
+ **fig. 1** · claude + harald find a sony wh-1000xm5's control channel over bluetooth classic, wait for you to arm it, then turn noise cancelling on. real hardware, sped up.
40
+
41
+ ## 一 how it works
42
+
43
+ harald is an mcp server, a cli and a playbook for coding agents. it gives your agent
44
+ hands and ears for bluetooth le and classic, so it can work out the protocol of a
45
+ gadget you own. then you control that gadget from your own code instead of the
46
+ vendor's app.
47
+
48
+ | *listen* | *decode* | *speak* |
49
+ | :--- | :--- | :--- |
50
+ | harald reads what the vendor app says to the gadget, from your phone's bluetooth log. | your agent works out the language, keys and checksums included, by diffing payloads, testing keys and searching the vendor's android app. | you get the protocol written up and a standalone driver script with its own small cli. no vendor app. |
51
+
52
+ ## 二 install
53
+
54
+ you need [uv](https://docs.astral.sh/uv/). then:
55
+
56
+ ```sh
57
+ uv tool install harald
58
+ ```
59
+
60
+ that puts `harald` on your path for the commands below. if your shell can't find it,
61
+ run `uv tool update-shell`. to try harald without installing it, run
62
+ `uvx harald --help`: uvx fetches harald and runs it once. tested on mac.
63
+
64
+ the first time harald scans, macos asks whether your terminal app may use bluetooth.
65
+ allow it. if you missed the prompt, turn it on in system settings › privacy & security
66
+ › bluetooth.
67
+
68
+ two optional tools, for the steps that need them:
69
+
70
+ - `brew install jadx` lets harald read a vendor's android app.
71
+ - `brew install libimobiledevice` records your iphone's bluetooth log from the terminal.
72
+
73
+ harald also works without an agent:
74
+
75
+ ```text
76
+ harald scan list bluetooth le devices nearby
77
+ harald connect <address> list a device's services and characteristics
78
+ harald read <address> <char> read a characteristic once
79
+ harald write <address> <char> <hex> write bytes, once you have armed the device
80
+ harald capture open <file> show a phone capture as a timeline
81
+ harald apk <file> search a vendor android app for uuids and keys
82
+ harald devices list the device library
83
+ harald drive <slug> <command> run a library device's driver
84
+ ```
85
+
86
+ `harald --help` lists the rest, and [bluetooth classic](#五-bluetooth-classic) has
87
+ the commands for classic gadgets.
88
+
89
+ ## 三 devices
90
+
91
+ | device | what harald found | status |
92
+ | :--- | :--- | :--- |
93
+ | sony wh-1000xm5 headphones | noise cancelling, ambient and battery over bluetooth classic | decoded |
94
+ | your device | open a pull request with its `device.toml`, `protocol.md` and `driver.py` | wanted |
95
+
96
+ each decoded device lives in
97
+ [`devices/`](https://github.com/k0nnect/harald/tree/main/devices) with its protocol
98
+ written up and a standalone driver. `harald devices` lists them, and
99
+ `harald drive <slug> <command>` runs one, for example `harald drive <slug> on`.
100
+
101
+ ## 四 agent setup
102
+
103
+ add the mcp server to your agent, then install the playbook: the step-by-step method
104
+ your agent follows to crack a gadget.
105
+
106
+ **claude code**, in your project folder:
107
+
108
+ ```sh
109
+ claude mcp add harald -- uvx harald mcp
110
+ harald playbook install claude-code --dest .claude/skills/harald
111
+ ```
112
+
113
+ use `--dest ~/.claude/skills/harald` to have the skill in every project.
114
+
115
+ **codex**
116
+
117
+ add this to `~/.codex/config.toml`, or run `codex mcp add harald -- uvx harald mcp`:
118
+
119
+ ```toml
120
+ [mcp_servers.harald]
121
+ command = "uvx"
122
+ args = ["harald", "mcp"]
123
+ ```
124
+
125
+ then, in your project folder:
126
+
127
+ ```sh
128
+ harald playbook install codex
129
+ ```
130
+
131
+ this writes `AGENTS.md`. if your project already has one, harald leaves it alone; use
132
+ `--dest ~/.codex` to install it for every project instead.
133
+
134
+ **cursor**
135
+
136
+ add this to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for every
137
+ project:
138
+
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "harald": {
143
+ "command": "uvx",
144
+ "args": ["harald", "mcp"]
145
+ }
146
+ }
147
+ }
148
+ ```
149
+
150
+ cursor has no playbook; it uses harald's tools directly.
151
+
152
+ then ask your agent to work out your gadget with harald. it will ask you for two things
153
+ only you can do.
154
+
155
+ **record your phone talking to the gadget.** a computer cannot overhear phone-to-gadget
156
+ traffic without extra hardware, so the log comes from your own iphone. a bluetooth
157
+ classic gadget's log comes from an android phone instead; see
158
+ [bluetooth classic](#五-bluetooth-classic).
159
+
160
+ 1. on the iphone, install apple's bluetooth logging profile: "bluetooth for ios/ipados"
161
+ on apple's [profiles and logs](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=bluetooth)
162
+ page. it is free.
163
+ 2. connect the iphone to your mac by cable. open packetlogger, from apple's
164
+ [additional tools for xcode](https://developer.apple.com/download/all/), and choose
165
+ file › new ios trace. or, from the terminal: `idevicebtlogger capture.pklg`, which
166
+ records until you press ctrl-c.
167
+ 3. in the vendor app, do one thing at a time, a few seconds apart, in the order your
168
+ agent asks for.
169
+ 4. stop the trace, save the `.pklg` file and give your agent its path. to look at it
170
+ yourself: `harald capture open capture.pklg`.
171
+
172
+ if packetlogger records nothing, remove the logging profile and install it again.
173
+
174
+ **get the vendor's android app.** if the commands look encrypted, your agent asks for
175
+ the apk of the vendor app you already use. you don't need an android phone, and
176
+ harald never downloads apps: download it yourself from
177
+ [apkmirror](https://www.apkmirror.com/), which verifies each app's signature, or with
178
+ [apkeep](https://github.com/EFForg/apkeep) (`apkeep -a <package name> .`). the package
179
+ name is the `id=` part of the app's play store address.
180
+
181
+ ## 五 bluetooth classic
182
+
183
+ some gadgets, often headphones and speakers, are controlled over bluetooth classic
184
+ instead of bluetooth le: the vendor app talks to an rfcomm channel, a plain byte
185
+ stream. harald reaches those too, on a mac only for now; bluetooth le works as before.
186
+
187
+ pair the gadget in system settings > bluetooth first and keep it connected there.
188
+ harald doesn't search for or pair classic gadgets, and `harald scan` only sees
189
+ bluetooth le. classic addresses look like `aa:bb:cc:11:22:33`.
190
+
191
+ ```text
192
+ harald classic devices list the gadgets paired with this mac
193
+ harald classic services <address> list a gadget's rfcomm services
194
+ harald classic send <address> <service> <hex> send messages, once you have armed it
195
+ harald classic listen <address> <service> show what a channel says, sending nothing
196
+ harald capture open <file> --channel n show one rfcomm channel of a capture
197
+ ```
198
+
199
+ `<service>` is a service uuid or channel number from `harald classic services`; add
200
+ `--refresh` there when the list looks out of date. `harald classic send` takes one or
201
+ more messages and sends them in order, listening for replies after each one
202
+ (`--listen s`, 2 seconds by default). if the grant runs out or the link fails
203
+ partway, it prints what it sent and received, says why on stderr and exits 1.
204
+ `harald classic listen` sends nothing and listens for `--seconds s` (5 by default).
205
+ either listens for at most 30 seconds at a time.
206
+
207
+ like `harald write`, `harald classic send` needs `harald arm <address>` first, and
208
+ every message spends one write from the grant, a query too. harald never opens the
209
+ channels of protected profiles such as hands-free or file transfer; see
210
+ [safety](#六-safety). your agent gets the same four as mcp tools: `classic_devices`,
211
+ `classic_services`, `classic_send` and `classic_listen`. none of them arms.
212
+
213
+ **classic captures come from android.** an iphone capture of a classic gadget shows
214
+ only apple's iap2 wrapper, which harald can't unwrap yet, so record the vendor app
215
+ from an android phone's bluetooth log:
216
+
217
+ 1. turn on developer options (tap the build number seven times in the phone's about
218
+ screen), then turn on the bluetooth hci snoop log in them, choosing enabled
219
+ rather than filtered if it asks.
220
+ 2. with the vendor app closed, turn the phone's bluetooth off and on, so the log is
221
+ running before the app opens its channel.
222
+ 3. in the vendor app, do one thing at a time, a few seconds apart, as your agent
223
+ asks.
224
+ 4. with usb debugging on, run `adb bugreport`. its zip holds `btsnoop_hci.log`; give
225
+ your agent that file's path, or look at it yourself with
226
+ `harald capture open btsnoop_hci.log`.
227
+
228
+ `harald apk` flags the rfcomm sockets and uuids in a vendor's android app as well as
229
+ its bluetooth le calls.
230
+
231
+ ## 六 safety
232
+
233
+ harald is for gadgets you own: interoperability and research. don't use it on anyone
234
+ else's devices.
235
+
236
+ - **writes need you.** harald refuses every write until you run, in your own terminal:
237
+
238
+ ```sh
239
+ harald arm <address> --writes n --minutes m
240
+ ```
241
+
242
+ that grants a budget of n writes for m minutes (50 and 30 if you leave the flags
243
+ out), and each write spends one. it shows the grant and waits for you to type
244
+ `arm`, and it refuses to run without a terminal. `harald status` shows what is
245
+ armed and `harald disarm <address>` revokes it. there is no mcp tool that arms.
246
+ - **the gate has limits.** `harald arm` stops casual or accidental arming by an
247
+ agent, but it is not a sandbox. grants live in a file, `~/.harald/arm.json`, and
248
+ any agent that can run arbitrary commands or write files on your machine could get
249
+ around the gate. what keeps a cooperative agent out is the playbook's rule: it
250
+ never runs `harald arm`, `harald write`, `harald drive`, `harald classic send` or
251
+ a driver from its own shell; you do. it sends classic messages only through its
252
+ `classic_send` tool.
253
+ - **firmware updates are blocked.** writes to firmware-update (dfu) services stay
254
+ refused even when a device is armed, so a stray write cannot start a firmware update.
255
+ only `--allow-dfu` on `harald arm` lifts that, and working out a protocol never
256
+ needs it. harald also blocks the classic firmware-update channels it knows, so far
257
+ airoha's tooling channel, the same way unless you arm with `--allow-dfu`. but many
258
+ classic gadgets take firmware over their normal control channel, where harald can't
259
+ tell an update from any other message, so your agent must never send a message
260
+ whose purpose it doesn't know.
261
+ - **classic channels are fenced.** harald never opens a channel whose service record
262
+ lists hands-free, headset, dial-up networking, obex object push and file transfer,
263
+ phonebook, messages, sim access or apple iap2. the serial port profile is allowed,
264
+ since many gadgets' control channels use it. harald also refuses channels missing
265
+ from the device's service list. with `harald classic send`, every message spends
266
+ one write, a query too.
267
+ - **library drivers need no arming.** `harald drive` runs a `devices/` entry's
268
+ standalone driver, which you start yourself, so it sits outside the gate. read a
269
+ driver before you run it, as you would any script.
270
+ - **only app-wide keys are shared.** a `devices/` entry may carry a key only if it is
271
+ the same on every unit and ships inside the vendor app. per-unit keys and pairing
272
+ keys never go in the library; they stay in your own `harald-work/` folder.
273
+
274
+ ## 七 contributing a device
275
+
276
+ cracked a gadget? your agent's work is in `harald-work/<slug>/`. turn it into
277
+ `devices/<slug>/` with a `device.toml`, `protocol.md` and `driver.py`, then open a pull
278
+ request. the
279
+ [contributing guide](https://github.com/k0nnect/harald/blob/main/CONTRIBUTING.md)
280
+ has the details, including the keys rule.
281
+
282
+ <sub>mit license. harald is named after king harald "bluetooth" of denmark, after whom
283
+ bluetooth is named; ᚼᛅᚱᛅᛚᛏᚱ is his name as it is carved on the jelling stone.</sub>