Skip to main content

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

Project description

nimbus-notify

A Python host broker that watches your AI coding-agent sessions — Claude Code, Codex, and Mistral Vibe — via lightweight hooks, and pushes their live status (running / waiting for you / needs approval / done / errored) to a physical display device over serial (USB-CDC) or Bluetooth LE, using a small documented binary protocol (nsn).

If you've got several agent sessions going in parallel across different terminals and projects, nimbus-notify gives you one glanceable place — an LED ring, an e-ink panel, whatever device you point it at — to see which ones are still working and which ones are waiting on you.

┌────────────┐   hooks    ┌───────────────┐   nsn wire protocol   ┌──────────┐
│ Claude Code│ ─────────▶ │               │   (serial or BLE)     │  status  │
│ Codex      │ ─────────▶ │ nsnotify-     │ ─────────────────────▶│  device  │
│ Mistral    │ ─────────▶ │  broker       │                       │(your own)│
│  Vibe      │            │               │                       │          │
└────────────┘            └───────────────┘                       └──────────┘

Status

Beta. The core broker, all three harness adapters, and both transports are implemented and tested (python3 -m pytest). This is a fresh split out of a private monorepo into its own package — see CHANGELOG.md.

Compatible devices

nimbus-notify speaks a documented, transport-agnostic wire protocol — see docs/protocol.md. Any device that implements the protocol's serial or BLE side can be driven by this broker; nothing here is tied to a specific piece of hardware. If you build (or already have) a microcontroller project with an LED strip, an e-ink panel, or any other status display, point it at this broker.

Install

Not yet on PyPI — install from a clone:

git clone https://github.com/ristllin/nimbus-notify.git
cd nimbus-notify
pip install -e .

This installs two commands on your PATH:

  • nimbus-notify-broker — the daemon that maintains session state and talks to your device.
  • led-report — the small CLI that harness hooks call to report events into the broker (fire-and-forget; never blocks your agent).

PyPI publishing (pip install nimbus-notify) is a planned next step — for now, the git-clone path above is the supported install method.

Quickstart

  1. Install the package (above).
  2. Wire up the harness(es) you use — see Harnesses below.
  3. Start the broker:
    nimbus-notify-broker
    
  4. Start (or resume) an agent session in a wired-up harness. Its status should now be reported to the broker, and forwarded to your device.

If you use Claude Code, the fastest path is the bundled slash commands — see Claude Code plugin below.

Harnesses

nimbus-notify supports three AI coding harnesses today. Each harness reports events (session start, a tool running, waiting on your approval, done, errored, session end) by calling led-report <harness> <verb> from a hook.

Claude Code

Merge hooks/claude/settings.json's hooks block into your ~/.claude/settings.json, preserving any hooks you already have (append to each event's array rather than replacing it). It wires up SessionStart, UserPromptSubmit, PreToolUse, Notification, Stop, StopFailure, and SessionEnd.

Codex

Merge hooks/codex/hooks.json into ~/.codex/hooks.json, then enable hooks and the notify bridge in ~/.codex/config.toml:

[features]
hooks = true

notify = ["led-report", "codex-notify"]

Mistral Vibe

Vibe has no native session start/stop hook, so the broker also runs a background watcher over ~/.vibe/logs/session/ to detect new sessions and infer human-in-the-loop waits (if a tool call starts but doesn't finish within a timeout, that's treated as "awaiting approval").

Enable experimental hooks in ~/.vibe/config.toml:

enable_experimental_hooks = true

Then merge hooks/vibe/hooks.toml into ~/.vibe/hooks.toml (requires Vibe v2.15.0+ for before_tool / after_tool / post_agent_turn).

Claude Code plugin

This repo is also a Claude Code plugin (.claude-plugin/plugin.json). If you install it as a plugin, two slash commands become available:

  • /nsnotify-setup — installs the Python package, merges the Claude Code hooks automatically, checks whether the broker is running, and prints the manual steps for Codex/Vibe.
  • /nsnotify-status — shows current session states without needing to look at the device.

Transports

Pick a transport with --transport:

nimbus-notify-broker --transport serial   # default
nimbus-notify-broker --transport ble
nimbus-notify-broker --transport auto     # serial if a device is plugged in at
                                      # startup, else BLE
  • Serial auto-detects a likely USB-CDC port (Espressif native-USB VID 0x303A, or common USB-UART bridge chips), or pin one explicitly: nimbus-notify-broker --transport serial --port /dev/cu.usbmodem101.
  • BLE requires your device to be powered on, flashed with firmware that advertises the nsn BLE service, and in range. Optionally pin a specific device: nimbus-notify-broker --transport ble --ble-address <address> (a CoreBluetooth UUID on macOS, a MAC address on Linux). Without --ble-address, the broker scans for the nsn service UUID.

Transport selection happens once at startup — there's no live failover between serial and BLE mid-session in this version.

Bonding the BLE link (one time)

Recent Nimbus firmware secures the BLE link (bonded + encrypted, LE Secure Connections), so a device won't accept frames from an un-bonded computer — this stops anyone in radio range from painting your ring. Bonding is automatic (macOS "Just Works", no code to type) — but there are two things to know:

  • Nimbus does not appear in System Settings → Bluetooth. That list only shows recognized device types (keyboards, mice, audio). A custom BLE peripheral is invisible there by design — don't look for it. Bonding happens on-demand when the broker first touches the device, not by picking it in a list.
  • Do the first bond with the broker in the foreground. macOS only completes a bond for a process running in your normal login session — a fully detached process (e.g. nohup … & disown) is too detached and the bond silently fails. So the first time, just run nimbus-notify-broker --transport ble in a normal terminal and leave it up for a few seconds. Once bonded, the bond persists on both the device (flash) and your Mac, and every session after that is transparent — you can then run it backgrounded or as a service (below).

To un-bond: on the device, Connectivity → Forget paired devices (or the FORGETBONDS console command); the Mac side clears on its own next connect.

The firmware also carries a dormant MITM/passkey mode (it shows a 6-digit code on its e-ink screen). It's off by default because macOS won't surface the passkey-entry dialog for a broker-triggered pairing of a custom peripheral, so that pairing can't complete without a companion app.

Running persistently

For day-to-day use you'll want the broker running in the background whenever you're coding, not started by hand each time.

macOS (launchd)

Create ~/Library/LaunchAgents/com.nimbus-notify.broker.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.nimbus-notify.broker</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/env</string>
    <string>nimbus-notify-broker</string>
    <string>--transport</string>
    <string>auto</string>
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/tmp/nimbus-notify-broker.log</string>
  <key>StandardErrorPath</key><string>/tmp/nimbus-notify-broker.log</string>
</dict>
</plist>

Then:

launchctl load ~/Library/LaunchAgents/com.nimbus-notify.broker.plist

Linux (systemd, user service)

Create ~/.config/systemd/user/nimbus-notify-broker.service:

[Unit]
Description=nimbus-notify broker

[Service]
ExecStart=%h/.local/bin/nimbus-notify-broker --transport auto
Restart=on-failure

[Install]
WantedBy=default.target

Then:

systemctl --user daemon-reload
systemctl --user enable --now nimbus-notify-broker

Adjust ExecStart to wherever pip install -e . put the entry point (check with which nimbus-notify-broker).

The nsn wire protocol

A short summary — full details in docs/protocol.md.

[SOF 0xAA] [LEN] [payload: LEN bytes] [CRC8-MAXIM]

The payload starts with magic byte 0x4E, then a sequence number, then up to 16 fixed-size segment records (state, hue, animation, LED span) plus a global brightness — enough for a device to render a full status frame from a single self-contained packet, no persistent client-side state required. The reference encoder/decoder is notify/broker/frame.py.

Repository layout

notify/
  broker/       frame.py (wire codec), segments.py, server.py, session.py
  cli/          led_report.py — the hook-facing CLI entry point
  harness/      base.py + claude.py, codex.py, vibe.py adapters
  transport/    serial_tx.py, ble_tx.py
  state.py      shared State/Anim enums + default styling
tests/          pytest suite for the above
hooks/          drop-in hook configs per harness
commands/       Claude Code plugin slash commands
docs/           protocol.md

Contributing

See CONTRIBUTING.md, including the versioning convention used for releases.

License

MIT — see LICENSE. Copyright (c) 2026 Roy Darnell.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nimbus_notify-1.0.0.tar.gz (40.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nimbus_notify-1.0.0-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file nimbus_notify-1.0.0.tar.gz.

File metadata

  • Download URL: nimbus_notify-1.0.0.tar.gz
  • Upload date:
  • Size: 40.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nimbus_notify-1.0.0.tar.gz
Algorithm Hash digest
SHA256 b79fc8c4d06e3f83352be2558419cf11a428955930f3aef8a10b892d9e4fcd64
MD5 2ba594af10e9b2b043b6f728b21ec6a5
BLAKE2b-256 08dd1c5ee6f9b9cdcf8e77b8e05fbea18f83266f2a201e236c726f7824a18fe5

See more details on using hashes here.

Provenance

The following attestation bundles were made for nimbus_notify-1.0.0.tar.gz:

Publisher: publish.yml on ristllin/nimbus-notify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nimbus_notify-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: nimbus_notify-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nimbus_notify-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d28e50639db4433587c2402461f7d6cc54c34ca36b5a4970f674f5243c034c0
MD5 2bed663e3ab65b6f063e5d6645318b42
BLAKE2b-256 1af219d7911de70e460c5d3e6f4d65934c9458ed78d3c433b4fe07359cbc66bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for nimbus_notify-1.0.0-py3-none-any.whl:

Publisher: publish.yml on ristllin/nimbus-notify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page