nimbus-notify 1.0.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.
- nimbus_notify-1.0.0/.claude-plugin/plugin.json +6 -0
- nimbus_notify-1.0.0/.github/workflows/ci.yml +29 -0
- nimbus_notify-1.0.0/.github/workflows/publish.yml +52 -0
- nimbus_notify-1.0.0/.gitignore +9 -0
- nimbus_notify-1.0.0/CHANGELOG.md +122 -0
- nimbus_notify-1.0.0/CONTRIBUTING.md +47 -0
- nimbus_notify-1.0.0/LICENSE +21 -0
- nimbus_notify-1.0.0/PKG-INFO +303 -0
- nimbus_notify-1.0.0/README.md +277 -0
- nimbus_notify-1.0.0/commands/nsnotify-setup.md +117 -0
- nimbus_notify-1.0.0/commands/nsnotify-status.md +52 -0
- nimbus_notify-1.0.0/docs/protocol.md +124 -0
- nimbus_notify-1.0.0/hooks/claude/settings.json +84 -0
- nimbus_notify-1.0.0/hooks/codex/hooks.json +73 -0
- nimbus_notify-1.0.0/hooks/vibe/hooks.toml +26 -0
- nimbus_notify-1.0.0/notify/__init__.py +0 -0
- nimbus_notify-1.0.0/notify/broker/__init__.py +0 -0
- nimbus_notify-1.0.0/notify/broker/frame.py +108 -0
- nimbus_notify-1.0.0/notify/broker/segments.py +99 -0
- nimbus_notify-1.0.0/notify/broker/server.py +371 -0
- nimbus_notify-1.0.0/notify/broker/session.py +67 -0
- nimbus_notify-1.0.0/notify/cli/__init__.py +0 -0
- nimbus_notify-1.0.0/notify/cli/led_report.py +96 -0
- nimbus_notify-1.0.0/notify/harness/__init__.py +0 -0
- nimbus_notify-1.0.0/notify/harness/base.py +19 -0
- nimbus_notify-1.0.0/notify/harness/claude.py +47 -0
- nimbus_notify-1.0.0/notify/harness/codex.py +43 -0
- nimbus_notify-1.0.0/notify/harness/vibe.py +165 -0
- nimbus_notify-1.0.0/notify/state.py +52 -0
- nimbus_notify-1.0.0/notify/transport/__init__.py +21 -0
- nimbus_notify-1.0.0/notify/transport/ble_tx.py +361 -0
- nimbus_notify-1.0.0/notify/transport/serial_tx.py +99 -0
- nimbus_notify-1.0.0/pyproject.toml +46 -0
- nimbus_notify-1.0.0/tests/__init__.py +0 -0
- nimbus_notify-1.0.0/tests/test_ble_transport.py +279 -0
- nimbus_notify-1.0.0/tests/test_frame.py +58 -0
- nimbus_notify-1.0.0/tests/test_segments.py +81 -0
- nimbus_notify-1.0.0/tests/test_session.py +54 -0
- nimbus_notify-1.0.0/tests/test_transport_select.py +103 -0
- nimbus_notify-1.0.0/tests/test_vibe_watcher.py +137 -0
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.11", "3.12"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- name: Install + test
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip build twine
|
|
25
|
+
python -m pip install -e .
|
|
26
|
+
python -m pip install pytest
|
|
27
|
+
python -m build
|
|
28
|
+
python -m twine check dist/*
|
|
29
|
+
pytest -q
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes nimbus-notify to PyPI via Trusted Publishing (OIDC) — NO stored token.
|
|
4
|
+
# Prereq (one-time, on pypi.org): Account -> Publishing -> add a "pending publisher"
|
|
5
|
+
# project: nimbus-notify | owner: ristllin | repo: nimbus-notify
|
|
6
|
+
# workflow: publish.yml | environment: pypi
|
|
7
|
+
# Then this runs on every published GitHub Release.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
release:
|
|
11
|
+
types: [published]
|
|
12
|
+
workflow_dispatch: # manual trigger (build-only; publish still gates on a release env)
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
name: Build sdist + wheel
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.12"
|
|
26
|
+
- name: Build
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade build twine
|
|
29
|
+
python -m build
|
|
30
|
+
python -m twine check dist/*
|
|
31
|
+
- uses: actions/upload-artifact@v4
|
|
32
|
+
with:
|
|
33
|
+
name: dist
|
|
34
|
+
path: dist/
|
|
35
|
+
|
|
36
|
+
publish:
|
|
37
|
+
name: Publish to PyPI (Trusted Publishing)
|
|
38
|
+
needs: build
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
if: github.event_name == 'release'
|
|
41
|
+
environment:
|
|
42
|
+
name: pypi
|
|
43
|
+
url: https://pypi.org/p/nimbus-notify
|
|
44
|
+
permissions:
|
|
45
|
+
id-token: write # OIDC token for Trusted Publishing — this is the auth
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/download-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: dist
|
|
50
|
+
path: dist/
|
|
51
|
+
- name: Publish
|
|
52
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/);
|
|
6
|
+
versioning follows the convention described in
|
|
7
|
+
[CONTRIBUTING.md](CONTRIBUTING.md#versioning) (semver-for-0.x pre-1.0).
|
|
8
|
+
|
|
9
|
+
## [1.0.0] — 2026-07-12
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Renamed the pip distribution `nsnotify` → `nimbus-notify`** (brand alignment
|
|
14
|
+
with the Nimbus device) and the console script `nsnotify-broker` →
|
|
15
|
+
`nimbus-notify-broker`. The import package stays **`notify`** and the Claude
|
|
16
|
+
Code plugin/skill names are unchanged. The nsn wire protocol is untouched.
|
|
17
|
+
- **First PyPI release** — `pip install nimbus-notify` (published via GitHub
|
|
18
|
+
Actions Trusted Publishing).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `nimbus-notify-broker --install-service` / `--uninstall-service`: install the
|
|
23
|
+
broker as an auto-starting service (macOS launchd / Linux systemd user unit) so
|
|
24
|
+
it survives a reboot. Wired into `/nsnotify-setup` after the one-time
|
|
25
|
+
foreground BLE bond.
|
|
26
|
+
|
|
27
|
+
## [0.4.1] — 2026-07-04
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Corrected the BLE bonding docs + broker hint to match reality. The Nimbus
|
|
32
|
+
firmware bonds via macOS **"Just Works"** (encrypted + bonded, no passkey to
|
|
33
|
+
type) — not a MITM passkey, because macOS won't surface a passkey dialog for a
|
|
34
|
+
custom peripheral paired by the broker. Two gotchas are now documented: Nimbus
|
|
35
|
+
never appears in the System Settings Bluetooth list (it's a custom peripheral),
|
|
36
|
+
and the **first** bond must be made with the broker in the **foreground** — a
|
|
37
|
+
fully detached (`nohup … &`) process can't complete it. Once bonded it's
|
|
38
|
+
transparent and can run backgrounded. (Verified end-to-end on hardware.)
|
|
39
|
+
|
|
40
|
+
## [0.4.0] — 2026-07-04
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **The BLE link now requires a one-time pairing.** The Nimbus firmware secured
|
|
45
|
+
its GATT server (bonded + MITM passkey), so the broker writes FRAME **with
|
|
46
|
+
response** (`response=True`): an unbonded write returns an insufficient-
|
|
47
|
+
encryption error, which is what makes macOS raise its native pairing sheet.
|
|
48
|
+
Pair once — System Settings > Bluetooth, enter the 6-digit code shown on the
|
|
49
|
+
device screen (also on its serial console) — and every session after is
|
|
50
|
+
transparent. First-run gets a clear "pair first" hint on the broker console.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- After the unbonded write is rejected, the broker now **re-drives the pending
|
|
55
|
+
frame** on a short timer until the link encrypts (macOS upgrades the *same*
|
|
56
|
+
connection in place with no reconnect, so nothing else would re-send it) — the
|
|
57
|
+
ring no longer stays stale through the pairing window.
|
|
58
|
+
- `_is_encryption_error` narrowed so it no longer swallows a bare ATT
|
|
59
|
+
"insufficient resources" (transient) or "not permitted" (config) as a pairing
|
|
60
|
+
failure — those must surface, not be silently dropped on a bonded link.
|
|
61
|
+
- The "pair first" hint re-arms on each reconnect (was suppressed forever after
|
|
62
|
+
the first emission).
|
|
63
|
+
|
|
64
|
+
## [0.3.0] — 2026-07-03
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **Vibe sessions are now detected.** The broker never started the
|
|
69
|
+
`VibeWatcher`, so Vibe sessions (which have no start/stop hook — only
|
|
70
|
+
`before_tool`/`after_tool`/`post_agent_turn`) were invisible: they never
|
|
71
|
+
appeared on the ring and never cleared. The broker now starts the watcher on
|
|
72
|
+
`~/.vibe/logs/session/` and routes `before_tool`/`after_tool` timing into its
|
|
73
|
+
HITL-inference tracker. Verified end-to-end: a real `vibe -p` session now
|
|
74
|
+
shows `start → running (tool) → done` on the device.
|
|
75
|
+
- `VibeWatcher.start()` no longer bails permanently when
|
|
76
|
+
`~/.vibe/logs/session/` doesn't exist yet — on a fresh Vibe install that
|
|
77
|
+
directory is created only on the first session, so the watcher now starts as
|
|
78
|
+
long as `~/.vibe/` is present and picks up the session dir when it appears.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- `Broker.handle_event` is now serialized with a lock: it is called from both
|
|
83
|
+
the asyncio socket handler (led-report events) and the VibeWatcher daemon
|
|
84
|
+
thread, so the segment allocator / sequence counter / frame push can no
|
|
85
|
+
longer interleave.
|
|
86
|
+
|
|
87
|
+
## [0.2.0] — 2026-07-03
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- `nimbus-notify-broker --ble-name <NAME>`: connect only to a BLE peripheral
|
|
92
|
+
advertising this exact name (still gated on the nsn service UUID). Lets
|
|
93
|
+
several boards running this firmware on one desk stay unambiguous — e.g. a
|
|
94
|
+
bench board named `Nimbus-BT` vs a production `Nimbus`. On macOS this is the
|
|
95
|
+
reliable discriminator since CoreBluetooth hides the MAC address.
|
|
96
|
+
|
|
97
|
+
## [0.1.0] — 2026-07-03
|
|
98
|
+
|
|
99
|
+
Initial public release, split out of a private monorepo into its own
|
|
100
|
+
standalone package.
|
|
101
|
+
|
|
102
|
+
### Added
|
|
103
|
+
|
|
104
|
+
- Broker daemon (`nimbus-notify-broker`) that maintains live session state over a
|
|
105
|
+
Unix socket and pushes nsn wire-protocol frames to a connected device.
|
|
106
|
+
- `led-report` CLI, invoked from harness hooks to report session events to
|
|
107
|
+
the broker (fire-and-forget, never blocks the calling harness).
|
|
108
|
+
- Harness adapters for **Claude Code**, **Codex**, and **Mistral Vibe**,
|
|
109
|
+
including Vibe's session-file watcher and HITL-inference timeout (Vibe has
|
|
110
|
+
no native session start/stop hook).
|
|
111
|
+
- Two transports: **serial** (USB-CDC, auto-detects Espressif native-USB and
|
|
112
|
+
common USB-UART bridge chips) and **BLE** (GATT central, with
|
|
113
|
+
scan/connect/serve/backoff reconnection, MTU negotiation, and full-state
|
|
114
|
+
resend on every reconnect). Select with `nimbus-notify-broker --transport
|
|
115
|
+
serial|ble|auto`.
|
|
116
|
+
- Drop-in hook configs for all three harnesses under `hooks/`.
|
|
117
|
+
- Claude Code plugin (`.claude-plugin/plugin.json` + `commands/`) providing
|
|
118
|
+
`/nsnotify-setup` and `/nsnotify-status`.
|
|
119
|
+
- `docs/protocol.md` — a standalone description of the nsn wire protocol for
|
|
120
|
+
anyone implementing a compatible device.
|
|
121
|
+
|
|
122
|
+
[0.1.0]: https://github.com/ristllin/nimbus-notify/releases/tag/v0.1.0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Issues and pull requests are welcome — bug reports, new harness adapters,
|
|
4
|
+
new transports, or devices you've gotten talking to the broker.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/ristllin/nimbus-notify.git
|
|
10
|
+
cd nimbus-notify
|
|
11
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
12
|
+
pip install -e .
|
|
13
|
+
pip install pytest pytest-asyncio
|
|
14
|
+
python3 -m pytest
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Versioning
|
|
18
|
+
|
|
19
|
+
Every meaningful release: bump `version` in `pyproject.toml`, add a
|
|
20
|
+
`CHANGELOG.md` entry describing it, and tag the commit —
|
|
21
|
+
`git tag -a vX.Y.Z -m "..."` — then push the tag so consumers can pin to a
|
|
22
|
+
specific release instead of tracking `main`.
|
|
23
|
+
|
|
24
|
+
Semver-for-0.x (pre-1.0): MAJOR stays `0`; MINOR bumps for a new or changed
|
|
25
|
+
public surface (CLI flags, hook payload shape, wire-protocol version,
|
|
26
|
+
breaking or not); PATCH for a fix or internal change with no public-surface
|
|
27
|
+
change. One release can bundle multiple logical commits under one bump —
|
|
28
|
+
just say so in the CHANGELOG entry.
|
|
29
|
+
|
|
30
|
+
## Adding a harness adapter
|
|
31
|
+
|
|
32
|
+
Look at `notify/harness/claude.py` (hook-driven) or `notify/harness/vibe.py`
|
|
33
|
+
(hook + background file-watcher, for harnesses without full session
|
|
34
|
+
lifecycle hooks) as templates. An adapter's job is to turn whatever your
|
|
35
|
+
harness gives you (hook stdin JSON, a log file, etc.) into a
|
|
36
|
+
`HarnessEvent` (`notify/harness/base.py`) that `led-report` sends to the
|
|
37
|
+
broker. Add a matching hook config under `hooks/<harness>/` and document the
|
|
38
|
+
wiring in the README.
|
|
39
|
+
|
|
40
|
+
## Adding a transport
|
|
41
|
+
|
|
42
|
+
Transports implement the `Transport` protocol in `notify/transport/__init__.py`
|
|
43
|
+
— just `send(frame: bytes) -> bool` and `close() -> None`. See
|
|
44
|
+
`notify/transport/serial_tx.py` for the simplest example and
|
|
45
|
+
`notify/transport/ble_tx.py` for a fuller one with reconnect/backoff. Wire
|
|
46
|
+
a new transport into the `--transport` flag in `notify/broker/server.py`'s
|
|
47
|
+
`_make_transport()`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roy Darnell
|
|
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.
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nimbus-notify
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Host broker that watches your AI coding sessions (Claude Code, Codex, Mistral Vibe) and pushes live status to a physical LED ring / e-ink device over a documented wire protocol
|
|
5
|
+
Project-URL: Homepage, https://github.com/ristllin/nimbus-notify
|
|
6
|
+
Project-URL: Repository, https://github.com/ristllin/nimbus-notify
|
|
7
|
+
Project-URL: Changelog, https://github.com/ristllin/nimbus-notify/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Roy Darnell
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ble,claude-code,codex,hooks,led,mistral-vibe,notifier,serial
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: aiohttp>=3.9
|
|
22
|
+
Requires-Dist: bleak>=0.22
|
|
23
|
+
Requires-Dist: pyserial>=3.5
|
|
24
|
+
Requires-Dist: watchdog>=4.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# nimbus-notify
|
|
28
|
+
|
|
29
|
+
A Python host broker that watches your AI coding-agent sessions — **Claude
|
|
30
|
+
Code**, **Codex**, and **Mistral Vibe** — via lightweight hooks, and pushes
|
|
31
|
+
their live status (running / waiting for you / needs approval / done /
|
|
32
|
+
errored) to a physical display device over serial (USB-CDC) or Bluetooth LE,
|
|
33
|
+
using a small documented binary protocol ([nsn](docs/protocol.md)).
|
|
34
|
+
|
|
35
|
+
If you've got several agent sessions going in parallel across different
|
|
36
|
+
terminals and projects, nimbus-notify gives you one glanceable place — an LED
|
|
37
|
+
ring, an e-ink panel, whatever device you point it at — to see which ones
|
|
38
|
+
are still working and which ones are waiting on you.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
┌────────────┐ hooks ┌───────────────┐ nsn wire protocol ┌──────────┐
|
|
42
|
+
│ Claude Code│ ─────────▶ │ │ (serial or BLE) │ status │
|
|
43
|
+
│ Codex │ ─────────▶ │ nsnotify- │ ─────────────────────▶│ device │
|
|
44
|
+
│ Mistral │ ─────────▶ │ broker │ │(your own)│
|
|
45
|
+
│ Vibe │ │ │ │ │
|
|
46
|
+
└────────────┘ └───────────────┘ └──────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Status
|
|
50
|
+
|
|
51
|
+
Beta. The core broker, all three harness adapters, and both transports are
|
|
52
|
+
implemented and tested (`python3 -m pytest`). This is a fresh split out of a
|
|
53
|
+
private monorepo into its own package — see [CHANGELOG.md](CHANGELOG.md).
|
|
54
|
+
|
|
55
|
+
## Compatible devices
|
|
56
|
+
|
|
57
|
+
nimbus-notify speaks a documented, transport-agnostic wire protocol — see
|
|
58
|
+
[docs/protocol.md](docs/protocol.md). Any device that implements the
|
|
59
|
+
protocol's serial or BLE side can be driven by this broker; nothing here is
|
|
60
|
+
tied to a specific piece of hardware. If you build (or already have) a
|
|
61
|
+
microcontroller project with an LED strip, an e-ink panel, or any other
|
|
62
|
+
status display, point it at this broker.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
Not yet on PyPI — install from a clone:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/ristllin/nimbus-notify.git
|
|
70
|
+
cd nimbus-notify
|
|
71
|
+
pip install -e .
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This installs two commands on your `PATH`:
|
|
75
|
+
|
|
76
|
+
- `nimbus-notify-broker` — the daemon that maintains session state and talks to
|
|
77
|
+
your device.
|
|
78
|
+
- `led-report` — the small CLI that harness hooks call to report events
|
|
79
|
+
into the broker (fire-and-forget; never blocks your agent).
|
|
80
|
+
|
|
81
|
+
PyPI publishing (`pip install nimbus-notify`) is a planned next step — for now,
|
|
82
|
+
the git-clone path above is the supported install method.
|
|
83
|
+
|
|
84
|
+
## Quickstart
|
|
85
|
+
|
|
86
|
+
1. Install the package (above).
|
|
87
|
+
2. Wire up the harness(es) you use — see [Harnesses](#harnesses) below.
|
|
88
|
+
3. Start the broker:
|
|
89
|
+
```bash
|
|
90
|
+
nimbus-notify-broker
|
|
91
|
+
```
|
|
92
|
+
4. Start (or resume) an agent session in a wired-up harness. Its status
|
|
93
|
+
should now be reported to the broker, and forwarded to your device.
|
|
94
|
+
|
|
95
|
+
If you use Claude Code, the fastest path is the bundled slash commands —
|
|
96
|
+
see [Claude Code plugin](#claude-code-plugin) below.
|
|
97
|
+
|
|
98
|
+
## Harnesses
|
|
99
|
+
|
|
100
|
+
nimbus-notify supports three AI coding harnesses today. Each harness reports
|
|
101
|
+
events (session start, a tool running, waiting on your approval, done,
|
|
102
|
+
errored, session end) by calling `led-report <harness> <verb>` from a hook.
|
|
103
|
+
|
|
104
|
+
### Claude Code
|
|
105
|
+
|
|
106
|
+
Merge [`hooks/claude/settings.json`](hooks/claude/settings.json)'s `hooks`
|
|
107
|
+
block into your `~/.claude/settings.json`, preserving any hooks you already
|
|
108
|
+
have (append to each event's array rather than replacing it). It wires up
|
|
109
|
+
`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `Notification`, `Stop`,
|
|
110
|
+
`StopFailure`, and `SessionEnd`.
|
|
111
|
+
|
|
112
|
+
### Codex
|
|
113
|
+
|
|
114
|
+
Merge [`hooks/codex/hooks.json`](hooks/codex/hooks.json) into
|
|
115
|
+
`~/.codex/hooks.json`, then enable hooks and the notify bridge in
|
|
116
|
+
`~/.codex/config.toml`:
|
|
117
|
+
|
|
118
|
+
```toml
|
|
119
|
+
[features]
|
|
120
|
+
hooks = true
|
|
121
|
+
|
|
122
|
+
notify = ["led-report", "codex-notify"]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Mistral Vibe
|
|
126
|
+
|
|
127
|
+
Vibe has no native session start/stop hook, so the broker also runs a
|
|
128
|
+
background watcher over `~/.vibe/logs/session/` to detect new sessions and
|
|
129
|
+
infer human-in-the-loop waits (if a tool call starts but doesn't finish
|
|
130
|
+
within a timeout, that's treated as "awaiting approval").
|
|
131
|
+
|
|
132
|
+
Enable experimental hooks in `~/.vibe/config.toml`:
|
|
133
|
+
|
|
134
|
+
```toml
|
|
135
|
+
enable_experimental_hooks = true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then merge [`hooks/vibe/hooks.toml`](hooks/vibe/hooks.toml) into
|
|
139
|
+
`~/.vibe/hooks.toml` (requires Vibe v2.15.0+ for `before_tool` /
|
|
140
|
+
`after_tool` / `post_agent_turn`).
|
|
141
|
+
|
|
142
|
+
### Claude Code plugin
|
|
143
|
+
|
|
144
|
+
This repo is also a Claude Code plugin (`.claude-plugin/plugin.json`). If
|
|
145
|
+
you install it as a plugin, two slash commands become available:
|
|
146
|
+
|
|
147
|
+
- `/nsnotify-setup` — installs the Python package, merges the Claude Code
|
|
148
|
+
hooks automatically, checks whether the broker is running, and prints
|
|
149
|
+
the manual steps for Codex/Vibe.
|
|
150
|
+
- `/nsnotify-status` — shows current session states without needing to look
|
|
151
|
+
at the device.
|
|
152
|
+
|
|
153
|
+
## Transports
|
|
154
|
+
|
|
155
|
+
Pick a transport with `--transport`:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
nimbus-notify-broker --transport serial # default
|
|
159
|
+
nimbus-notify-broker --transport ble
|
|
160
|
+
nimbus-notify-broker --transport auto # serial if a device is plugged in at
|
|
161
|
+
# startup, else BLE
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Serial** auto-detects a likely USB-CDC port (Espressif native-USB VID
|
|
165
|
+
`0x303A`, or common USB-UART bridge chips), or pin one explicitly:
|
|
166
|
+
`nimbus-notify-broker --transport serial --port /dev/cu.usbmodem101`.
|
|
167
|
+
- **BLE** requires your device to be powered on, flashed with firmware that
|
|
168
|
+
advertises the nsn BLE service, and in range. Optionally pin a specific
|
|
169
|
+
device: `nimbus-notify-broker --transport ble --ble-address <address>`
|
|
170
|
+
(a CoreBluetooth UUID on macOS, a MAC address on Linux). Without
|
|
171
|
+
`--ble-address`, the broker scans for the nsn service UUID.
|
|
172
|
+
|
|
173
|
+
Transport selection happens once at startup — there's no live failover
|
|
174
|
+
between serial and BLE mid-session in this version.
|
|
175
|
+
|
|
176
|
+
### Bonding the BLE link (one time)
|
|
177
|
+
|
|
178
|
+
Recent Nimbus firmware **secures the BLE link** (bonded + encrypted, LE Secure
|
|
179
|
+
Connections), so a device won't accept frames from an un-bonded computer — this
|
|
180
|
+
stops anyone in radio range from painting your ring. Bonding is **automatic**
|
|
181
|
+
(macOS "Just Works", no code to type) — but there are two things to know:
|
|
182
|
+
|
|
183
|
+
- **Nimbus does *not* appear in System Settings → Bluetooth.** That list only
|
|
184
|
+
shows recognized device types (keyboards, mice, audio). A custom BLE
|
|
185
|
+
peripheral is invisible there by design — don't look for it. Bonding happens
|
|
186
|
+
on-demand when the broker first touches the device, not by picking it in a
|
|
187
|
+
list.
|
|
188
|
+
- **Do the first bond with the broker in the foreground.** macOS only completes
|
|
189
|
+
a bond for a process running in your normal login session — a fully detached
|
|
190
|
+
process (e.g. `nohup … & disown`) is too detached and the bond silently fails.
|
|
191
|
+
So the *first* time, just run `nimbus-notify-broker --transport ble` in a normal
|
|
192
|
+
terminal and leave it up for a few seconds. Once bonded, the bond persists on
|
|
193
|
+
both the device (flash) and your Mac, and every session after that is
|
|
194
|
+
transparent — you can then run it backgrounded or as a service (below).
|
|
195
|
+
|
|
196
|
+
To un-bond: on the device, *Connectivity → Forget paired devices* (or the
|
|
197
|
+
`FORGETBONDS` console command); the Mac side clears on its own next connect.
|
|
198
|
+
|
|
199
|
+
> The firmware also carries a dormant MITM/passkey mode (it shows a 6-digit code
|
|
200
|
+
> on its e-ink screen). It's off by default because macOS won't surface the
|
|
201
|
+
> passkey-entry dialog for a broker-triggered pairing of a custom peripheral, so
|
|
202
|
+
> that pairing can't complete without a companion app.
|
|
203
|
+
|
|
204
|
+
## Running persistently
|
|
205
|
+
|
|
206
|
+
For day-to-day use you'll want the broker running in the background
|
|
207
|
+
whenever you're coding, not started by hand each time.
|
|
208
|
+
|
|
209
|
+
### macOS (launchd)
|
|
210
|
+
|
|
211
|
+
Create `~/Library/LaunchAgents/com.nimbus-notify.broker.plist`:
|
|
212
|
+
|
|
213
|
+
```xml
|
|
214
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
215
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
216
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
217
|
+
<plist version="1.0">
|
|
218
|
+
<dict>
|
|
219
|
+
<key>Label</key><string>com.nimbus-notify.broker</string>
|
|
220
|
+
<key>ProgramArguments</key>
|
|
221
|
+
<array>
|
|
222
|
+
<string>/usr/bin/env</string>
|
|
223
|
+
<string>nimbus-notify-broker</string>
|
|
224
|
+
<string>--transport</string>
|
|
225
|
+
<string>auto</string>
|
|
226
|
+
</array>
|
|
227
|
+
<key>RunAtLoad</key><true/>
|
|
228
|
+
<key>KeepAlive</key><true/>
|
|
229
|
+
<key>StandardOutPath</key><string>/tmp/nimbus-notify-broker.log</string>
|
|
230
|
+
<key>StandardErrorPath</key><string>/tmp/nimbus-notify-broker.log</string>
|
|
231
|
+
</dict>
|
|
232
|
+
</plist>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Then:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
launchctl load ~/Library/LaunchAgents/com.nimbus-notify.broker.plist
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Linux (systemd, user service)
|
|
242
|
+
|
|
243
|
+
Create `~/.config/systemd/user/nimbus-notify-broker.service`:
|
|
244
|
+
|
|
245
|
+
```ini
|
|
246
|
+
[Unit]
|
|
247
|
+
Description=nimbus-notify broker
|
|
248
|
+
|
|
249
|
+
[Service]
|
|
250
|
+
ExecStart=%h/.local/bin/nimbus-notify-broker --transport auto
|
|
251
|
+
Restart=on-failure
|
|
252
|
+
|
|
253
|
+
[Install]
|
|
254
|
+
WantedBy=default.target
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Then:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
systemctl --user daemon-reload
|
|
261
|
+
systemctl --user enable --now nimbus-notify-broker
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Adjust `ExecStart` to wherever `pip install -e .` put the entry point
|
|
265
|
+
(check with `which nimbus-notify-broker`).
|
|
266
|
+
|
|
267
|
+
## The nsn wire protocol
|
|
268
|
+
|
|
269
|
+
A short summary — full details in [docs/protocol.md](docs/protocol.md).
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
[SOF 0xAA] [LEN] [payload: LEN bytes] [CRC8-MAXIM]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The payload starts with magic byte `0x4E`, then a sequence number, then up
|
|
276
|
+
to 16 fixed-size segment records (state, hue, animation, LED span) plus a
|
|
277
|
+
global brightness — enough for a device to render a full status frame from
|
|
278
|
+
a single self-contained packet, no persistent client-side state required.
|
|
279
|
+
The reference encoder/decoder is [`notify/broker/frame.py`](notify/broker/frame.py).
|
|
280
|
+
|
|
281
|
+
## Repository layout
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
notify/
|
|
285
|
+
broker/ frame.py (wire codec), segments.py, server.py, session.py
|
|
286
|
+
cli/ led_report.py — the hook-facing CLI entry point
|
|
287
|
+
harness/ base.py + claude.py, codex.py, vibe.py adapters
|
|
288
|
+
transport/ serial_tx.py, ble_tx.py
|
|
289
|
+
state.py shared State/Anim enums + default styling
|
|
290
|
+
tests/ pytest suite for the above
|
|
291
|
+
hooks/ drop-in hook configs per harness
|
|
292
|
+
commands/ Claude Code plugin slash commands
|
|
293
|
+
docs/ protocol.md
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Contributing
|
|
297
|
+
|
|
298
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md), including the versioning
|
|
299
|
+
convention used for releases.
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Roy Darnell.
|