# Blinky starter

A portable kit for an agent programming TABB's USB light. Start with `AGENTS.md`.
The runtime is Mac first: Python 3.9 or later, standard library only, using USB
CDC. Other operating systems need their own serial-port discovery and compatible
upload tools; the wire protocol stays the same.

## Control without flashing

From this directory:

```sh
python3 host/nanolight.py --list
python3 host/nanolight.py --port DISCOVERED_PORT
python3 host/nanolight.py --port DISCOVERED_PORT 004000
python3 host/nanolight.py --port DISCOVERED_PORT 000000
```

Replace `DISCOVERED_PORT` with the actual connected device, typically a
`/dev/cu.usbmodemCH55x…` path on macOS. A successful identity query prints
`NanoLight r09`; a successful color command prints `OK`. This identity is also
used with the electrically compatible r10 board. The application uses the
development VID:PID `1209:c550`; USB serial strings are shared, not unique.

The protocol has exactly two commands, ASCII with a trailing newline:

| Command | Reply |
| --- | --- |
| `NL1 INFO\n` | `NanoLight r09\n` |
| `NL1 RRGGBB\n` | `OK\n`, or `ERR\n` on invalid input |

RGB channels are 0–255; `000000` is off. Try moderate brightness first. For
effects, reuse `serial_port`, `command`, and `color_packet` from
`host/nanolight.py`, sending a new RGB frame on a timer. These functions work on
an already-open port, so an animation need not spawn a process for every frame.
There is no firmware `BLINK`, `RAINBOW`, HTTP, or Wi-Fi command. The normal image
starts off and blanks during USB suspend or while unconfigured.

Use one host controller per device. Inspect existing Blinky services before
starting a new one; another worker can overwrite colors. Recheck identity on
reconnection. If several lights are connected, get the intended port rather
than choosing one silently. USB `OK` confirms accepted commands, not observed
LED color. Provide a finite test before installing a persistent controller.

## Public data first

For internet-triggered behavior, leave USB firmware alone and run the watcher
on the computer. Prefer local events, public HTTPS JSON, RSS/Atom, and other
sources that return useful data with no account, cookies, login, keys, or
subscription. Verify an anonymous request before committing to a source. If a
request truly requires a private account, explain the missing access and offer
a public substitute that preserves the user's intent.

An anonymous request to MLB's own API was verified on 2026-09-28:

```text
https://statsapi.mlb.com/api/v1/teams/119
https://statsapi.mlb.com/api/v1/schedule?sportId=1&teamId=119&date=YYYY-MM-DD&hydrate=linescore
```

Use the user's local date at runtime. Dodgers team ID is 119. In
`dates[].games[]`, select the `teams.home` or `teams.away` object with
`team.id == 119`, and compare `score` by `gamePk`. First fetch establishes a
baseline; a later increase triggers blue. Handle doubleheaders, score
corrections, missing scores, and reconnects without replaying an old event.
Persist a baseline if the watcher should survive restarts. A practical starting
interval is 30 seconds during live play, with slower checks between games,
request timeouts and exponential backoff after errors; no official service
guarantee or rate limit was verified. Do not turn a network error into a score.

## Change or restore firmware

The kit contains the existing source, vendor notices, two prebuilt images and
the pinned toolchain manifest. Normal control and the rainbow demo have been
flashed to the physical r09 board and exercised on macOS. Historical source
comments saying all hardware testing is pending predate that bring-up; they are
not proof that every electrical or mechanical property has been qualified.

For an autonomous behavior such as a power-on animation, edit
`firmware/NanoLight.c`. This is C compiled with SDCC for the CH552P 8051, not
Arduino C++ or CircuitPython. The controller uses its 16 MHz internal clock.
LED DIN is P1.4 / Arduino pin 14. `show(r,g,b)` converts RGB to the LED's GRB
wire order; the bundled driver and 250 µs reset interval are intentional.
Keep the USB descriptors, command interface and update path functional.

```sh
python3 firmware/bootstrap.py
python3 firmware/build.py
python3 host/flash.py firmware/build/NanoLight.hex --port DISCOVERED_PORT
```

Bootstrap downloads public archives and verifies locked SHA-256 checksums.
No account is required. The compiler and uploader are macOS Intel binaries;
Apple Silicon needs Rosetta. The build and flasher reject application addresses
at or above `0x3800`, protecting the factory bootloader. The uploader uses
`-t CH552 -c 3` (P3.6 / USB D+ recovery setting). The normal update checks identity,
then requests the bootloader by lowering DTR at 1200 baud. Only the intended
CH55x ISP device should be connected, because the uploader does not select by a
unique USB serial. ISP VID:PID is `4348:55e0`.

For recovery, bridge TP1–TP2 while inserting the board, remove the bridge after
ISP enumeration, and run:

```sh
python3 host/flash.py firmware/build/NanoLight.hex --recovery
```

Use only the board's intended recovery contacts; an agent cannot bridge them
remotely. This depends on the factory flash bootloader remaining intact.

For the supplied autonomous rainbow:

```sh
NANOLIGHT_DEMO=1 python3 firmware/build.py
python3 host/flash.py firmware/build-demo/NanoLight.hex --port DISCOVERED_PORT
```

The demo also runs on power-only USB and deliberately continues during suspend.
A host color command pauses it until the next power cycle. Restore
`firmware/build/NanoLight.hex` for normal USB suspend behavior. Pause any
competing light controller before showing or flashing an autonomous demo.

## Codex attention light

`codex/codex_light.py` is the existing receiver and reconnecting worker;
`codex/codex_click.swift` clears green on a click while Codex is foreground.
Green and orange each blink at roughly one cycle per second. The worker uses
the same host client and only stores lifecycle metadata, not chat contents.
This integration is for local Codex tasks on the connected Mac.
The existing worker drives every compatible Blinky it finds. For a selected
device among several, the agent must add an explicit port filter before running
it; otherwise use it with a single attached Blinky.

Consult the [current official hook reference](https://learn.chatgpt.com/docs/hooks)
and the installed Codex version before configuring hooks. Supported events in
the validated integration are `Stop`, `PermissionRequest`, `PreToolUse`,
`PostToolUse`, `UserPromptSubmit`, `Interrupt`, and `SessionEnd`. Use observer
hooks that call `codex_light.py hook` and return `{}`. Merge with existing hook
entries and review/trust the exact definitions through Codex's normal `/hooks`
interface. Do not bypass trust, overwrite unrelated configuration, or make a
hook approve or reject actions.

The packaged receiver imports from the kit's `host/` directory. Alternatively,
install the receiver beside `nanolight.py` in a chosen local directory. Compile
the click helper there as `codex-click`, and set `NANOLIGHT_HOME` consistently
for both the hooks and the worker. Run `codex_light.py worker` as a supervised
login service for automatic startup. There is no installer in this kit; the
agent should generate paths and service settings for the actual computer.

Expected behavior: `Stop` → green; approval/input request → orange;
`UserPromptSubmit` → off; matching synchronous-question/tool completion → off;
interruption/session end → off. Pending orange across tasks wins over green.
Asynchronous questions remain orange until a reply. Permission orange clears
after the approved tool returns, not necessarily when the approval button is
clicked. The click helper clears green only. Restart existing desktop sessions
after hook setup and verify actual events, not just synthetic inputs.

## Offline checks

```sh
python3 -m unittest discover -s tests -p 'test_*.py'
python3 -m unittest discover -s codex -p 'test_*.py'
cc -std=c99 tests/test_protocol.c firmware/protocol.c -o /tmp/blinky-protocol-test
/tmp/blinky-protocol-test
python3 host/flash.py firmware/build/NanoLight.hex --recovery --dry-run
```

These checks use simulated serial ports and image validation; they do not
write a connected board. Keep third-party source/license notices when
redistributing the firmware.

## Hosting

The public entry point is `https://blink.experiments.team/`. The instruction
file is at `https://blink.experiments.team/AGENTS.md` and the companion download
is at `https://blink.experiments.team/blinky-starter.zip`, publicly readable
without login.
Verify both URLs after deployment. For a local delivery, give an agent the
instruction file and starter ZIP together as attachments or local files.
