{"id":51319816,"url":"https://github.com/riscue/claude-led","last_synced_at":"2026-07-01T12:01:37.511Z","repository":{"id":367828005,"uuid":"1282317954","full_name":"Riscue/claude-led","owner":"Riscue","description":"Physical status indicator for Claude Code. WS2812B + ESP8266 + USB-serial, hooks-driven. idle/think/tool/wait/success/error rendered as color + animation.","archived":false,"fork":false,"pushed_at":"2026-06-27T21:22:12.000Z","size":20,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-27T22:13:17.005Z","etag":null,"topics":["arduino","claude","claude-code","esp8266","led","status-indicator","ws2812b"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Riscue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-27T16:01:15.000Z","updated_at":"2026-06-27T21:50:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Riscue/claude-led","commit_stats":null,"previous_names":["riscue/claude-led"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Riscue/claude-led","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riscue%2Fclaude-led","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riscue%2Fclaude-led/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riscue%2Fclaude-led/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riscue%2Fclaude-led/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Riscue","download_url":"https://codeload.github.com/Riscue/claude-led/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Riscue%2Fclaude-led/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35005413,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","claude","claude-code","esp8266","led","status-indicator","ws2812b"],"created_at":"2026-07-01T12:01:36.432Z","updated_at":"2026-07-01T12:01:37.496Z","avatar_url":"https://github.com/Riscue.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Claude Code LED Status Indicator\n\nA hardware indicator that turns a WS2812B LED strip into a status display for\nClaude Code (idle / thinking / running a tool / waiting for input / success / error).\n\n## Architecture\n\n```\nClaude Code --hooks--\u003e led --AF_UNIX--\u003e led_daemon.py --USB-serial--\u003e Wemos D1 Mini --WS2812B--\u003e strip\n                       (thin client)  (persistent service, mandatory)\n```\n\n- **No Wi-Fi.** The D1 Mini's wireless feature is never used — USB-serial only.\n- **Single cable.** Power and data both go through the same USB cable; no external power supply needed.\n- **Daemon keeps the port open, and is mandatory.** The ESP8266 resets on every serial-open (CH340 DTR line). A persistent daemon avoids the reset (and the 0.5 s wait + brief dark flash on back-to-back hooks). If the daemon is not running, the CLI drops the command instead of falling back to direct-serial — make sure `install.sh install` (auto-start at login) or `install.sh start` has been run.\n- **Install-and-forget.** `sudo ./scripts/install.sh install` copies the binaries to `/opt/claude-led/`, exposes them on `$PATH` as `led`, and registers a launchd/systemd user unit. Claude Code keeps working even if you delete this repo.\n- Brightness is capped in firmware (USB port safety); each command can additionally dim below that ceiling via a\n  `bright_pct` parameter.\n\n## Hardware requirements\n\n| Part                                                     | Note                                                   |\n|----------------------------------------------------------|--------------------------------------------------------|\n| WS2812B addressable LED strip                            | Any WS2812B / NeoPixel strip, stick, or ring works     |\n| Wemos D1 Mini (ESP8266, CH340 or CP2104 USB-serial chip) | Clone or original both work                            |\n| USB data cable (micro-USB)                               | A charge-only cable will NOT work — it must carry data |\n| 3 wires (~10 cm): 5V, GND, DIN                           | From the strip connector to the D1 Mini                |\n| Multimeter                                               | For pin verification — CRITICAL                        |\n\n\u003e A NodeMCU or any other ESP8266 board also works instead of the Wemos D1 Mini, but the pin mapping will differ.\n\n## Folders\n\n**Repo (development):**\n\n- `firmware/` — ESP8266 firmware (PlatformIO project)\n- `driver/led_cli.py` — Thin CLI client (becomes `/opt/claude-led/led_cli.py` after install)\n- `driver/led_daemon.py` — Persistent daemon (becomes `/opt/claude-led/led_daemon.py`)\n- `driver/states/` — JSON state profiles (copied to `/opt/claude-led/states/`)\n- `scripts/install.sh` — Install / uninstall / daemon control. Templates for launchd/systemd are embedded inline.\n- `examples/claude_settings_hooks_example.json` — Example Claude Code hook configuration\n\n**After `install.sh install` (system-wide, root-owned):**\n\n```\n/opt/claude-led/\n├── led_cli.py          # also accessible as `led` via /usr/local/bin/led\n├── led_daemon.py\n├── states/*.json\n└── install.sh          # so you can uninstall without the repo\n/usr/local/bin/led      # symlink -\u003e /opt/claude-led/led_cli.py\n```\n\n## Setup sequence\n\n### 1) Verifying the strip pinout (MOST CRITICAL STEP)\n\nIdentify the three connections at the input end of your WS2812B strip: 5V (VCC),\nGND, and DIN (data input). Most strips are labeled, but verify with a multimeter\nbefore wiring — do not guess. Wiring 5V and GND backwards will permanently kill\nthe LED chips. Convention is typically red=5V, black=GND, white or yellow=data,\nbut always confirm your strip's own color coding.\n\n### 2) Flashing the firmware (PlatformIO)\n\n- If PlatformIO is not installed: `pip3 install platformio` (or `brew install platformio`)\n- Plug the D1 Mini in via USB\n- From the project root: `cd firmware \u0026\u0026 pio run -t upload`\n    - Dependencies (`espressif8266@4.2.1` platform, `Adafruit NeoPixel@1.15.5` library)\n      are downloaded automatically; versions are pinned in `platformio.ini`.\n\n\u003e We avoid the Arduino IDE — its manual setup steps cause reproducibility issues.\n\u003e PlatformIO installs everything with a single command.\n\n### 3) Wiring\n\n```\nD1 Mini \"5V\"  -\u003e Strip 5V\nD1 Mini \"G\"   -\u003e Strip GND\nD1 Mini \"D4\"  -\u003e Strip DIN (data)\n```\n\n### 4) Host-side driver\n\n```bash\npip3 install pyserial\n```\n\nYou can run from the repo without installing:\n\n```bash\n# State mode — looks up claude.idle in driver/states/claude.json:\npython3 driver/led_cli.py --state claude.idle\n\n# Raw mode — direct animation, bypasses state profiles:\npython3 driver/led_cli.py --raw breathe --rgb 0,50,220 --period 3500\n```\n\n### 5) Install system-wide (recommended)\n\nInstall the binaries to `/opt/claude-led/`, expose `led` on `$PATH`, and register a launchd (macOS) / systemd user (Linux) unit so the daemon auto-starts at login:\n\n```bash\nsudo ./scripts/install.sh install\n```\n\nAfter install, the `led` command is on `$PATH` regardless of whether this repo exists:\n\n```bash\nled --state claude.idle                 # state lookup\nled --raw breathe --rgb 0,50,220 --period 3500\nled --direct --state claude.idle        # bypass daemon (debug)\n./scripts/install.sh status             # daemon status\n./scripts/install.sh logs               # tail daemon log\n./scripts/install.sh foreground         # run daemon in foreground (debug)\nsudo ./scripts/install.sh uninstall     # remove everything\n```\n\nThe daemon is mandatory: if it is not running, hook commands are dropped (the LED is not updated). Make sure to either run `install.sh install` (auto-start at login) or start the daemon manually with `./scripts/install.sh start`.\n\n### 6) Wiring up Claude Code hooks\n\nAppend the contents of `examples/claude_settings_hooks_example.json` to `~/.claude/settings.json`. The hooks call the installed `led` command, so no path editing is needed (only `install.sh install` must have been run).\n\n## Claude Code states\n\nThe driver ships with `driver/states/claude.json`, which maps each Claude Code\nstate to an animation. Edit that file to retune any state — **no firmware\nreflash, no Python changes required**.\n\n| State (`--state claude.\u003ckey\u003e`) | Animation | Color (RGB)   | Period | Brightness |\n|--------------------------------|-----------|---------------|--------|------------|\n| `idle`                         | breathe   | 0, 50, 220    | 3500ms | 100%       |\n| `thinking`                     | scanner   | 90, 0, 170    | 1600ms | 100%       |\n| `tool`                         | breathe   | 255, 128, 0   | 1500ms | 100%       |\n| `waiting`                      | breathe   | 200, 200, 200 | 2500ms | 60%        |\n| `success`                      | fill      | 0, 220, 0     | 3500ms | 100%       |\n| `error`                        | blink     | 180, 0, 0     | 300ms  | 100%       |\n| `off`                          | off       | —             | —      | —          |\n\n## Wire protocol\n\nThe firmware is generic — it does not know about Claude Code, only the\nanimation commands below. Each is a single ASCII line, lowercase, newline-\nterminated, at 115200 baud. `bright_pct` is optional (default 100) and scales\nbelow the firmware's `MAX_BRIGHTNESS` USB-safety ceiling.\n\n```\nsolid   r g b [bright_pct]              all LEDs steady\nbreathe r g b period_ms [bright_pct]    black -\u003e color, sin-based pulse\nblink   r g b period_ms [bright_pct]    period/2 on + period/2 off\nscanner r g b period_ms [bright_pct]    dot sweeps back and forth\nfill    r g b period_ms [bright_pct]    LEDs light one-by-one, then hold\noff\n```\n\nRGB is decimal 0-255 per channel. Period is in milliseconds (clamped to \u003e= 50\nin firmware). Unknown animations and malformed lines are silently ignored.\n\n## Customizing the visuals\n\nEdit `/opt/claude-led/states/claude.json` (after install) to retune any Claude\nCode state — change its animation, RGB, period, or brightness. Changes take\neffect on the next hook fire; no reflash, no Python edit. (During development,\nedit `driver/states/claude.json` in the repo and re-run `install.sh install`\nto copy it across.)\n\n```json\n\"error\": {\"animation\": \"blink\", \"rgb\": [180, 0, 0], \"period\": 500, \"brightness\": 70}\n```\n\n### Adding new state profiles\n\nState profiles are just JSON files. Drop a new file in `/opt/claude-led/states/`\n(e.g. `git.json`) and reference it with `--state git.\u003ckey\u003e`. Each entry needs\n`animation` plus `rgb` / `period` / `brightness` (except `off`, which needs\nonly `animation`). No Python changes required.\n\n```bash\nled --quiet --state git.merging\n```\n\nFor one-off testing from the shell without writing a profile, use `--raw`:\n\n```bash\nled --raw scanner --rgb 200,0,255 --period 1200 --brightness 50\n```\n\n## Claude Code hooks → state mapping\n\n| Hook (Claude Code event) | `--state` argument  | When it fires                            |\n|--------------------------|---------------------|------------------------------------------|\n| `SessionStart`           | `claude.idle`       | When Claude Code opens                   |\n| `UserPromptSubmit`       | `claude.thinking`   | When you send a message                  |\n| `PreToolUse`             | `claude.tool`       | Before a tool (Read/Bash/...) is invoked |\n| `PostToolUse`            | `claude.thinking`   | After a tool finishes                    |\n| `PostToolUseFailure`     | `claude.error`      | On a tool failure                        |\n| `Notification`           | `claude.waiting`    | When Claude Code shows a notification    |\n| `Stop`                   | `claude.success`    | When Claude Code finishes its response   |\n| `SessionEnd`             | `claude.off`        | When the session closes                  |\n\n\u003e Hooks call the installed `led` command directly. After `sudo ./scripts/install.sh install`\n\u003e no environment variables or path edits are needed — Claude Code finds `led` on `$PATH`.\n\n## Notes / limitations\n\n- `led` always exits with code 0 even if the LED hardware is missing or not found,\n  so it never disrupts the Claude Code flow.\n\n\u003e All states are persistent — the effect continues until a new hook/command arrives.\n\u003e There is no automatic return to blue (state changes as the user sends new commands).\n\n- **LEDs are not lighting up.** List serial ports with `ls /dev/cu.*`. If there is no\n  `cu.wchusbserial*` entry, either the USB cable is not carrying data or the CH340 driver\n  is missing (install the macOS driver from wch.cn).\n- **Port found but LEDs still not lighting up.** Re-verify the strip pins with a multimeter.\n  If you swap 5V and GND, the WS2812B chips will die permanently.\n- **When a hook fires, the LEDs briefly turn off and then back on.** This should not happen in\n  normal operation — the daemon keeps the serial port open so the ESP8266 does not reset. If you\n  see it, check `./scripts/install.sh status` and `./scripts/install.sh logs`. It is expected\n  with `led --direct ...` (debug), which opens the serial port per-command and pays the 0.5 s\n  ESP8266 reset wait.\n- **\"pyserial not installed\" warning.** Run `pip3 install pyserial`. The hook config example\n  calls the driver with `--quiet`, so this warning is hidden in the hook flow; it only\n  appears when running manually.\n- **Wrong port is being selected.** If multiple USB-serial devices are plugged in, set the\n  `CLAUDE_LED_PORT` environment variable or pass `--port /dev/cu.usbserial-XXXX`.\n\n---\n\n## License\n\nMIT © [Riscue](https://github.com/riscue)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friscue%2Fclaude-led","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Friscue%2Fclaude-led","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friscue%2Fclaude-led/lists"}