Agent status plugin that hooks every lifecycle event in your coding
session and publishes a small NDJSON state stream to a local TCP
broker. Built as a Claude Code plugin, and not Claude-specific: any
agent that implements Claude Code's plugin architecture (lifecycle
hooks with Claude-shaped payloads, CLAUDE_PLUGIN_ROOT /
CLAUDE_PLUGIN_DATA, markdown slash commands) can drive the panel —
GitHub Copilot CLI is the tested example. The bridge subscribes to that broker and forwards
each state to the ESP32-S3 firmware, which renders it on
a HUB75 LED panel sitting on your desk.
This repo is the umbrella — full system overview, hardware selection, and install walkthrough live on the project site:
→ https://sep.github.io/cc-status-plugin/
Claude Code ──► Plugin ──► Bridge ──► Firmware ──► Display
(this repo) (cross- (ESP32-S3) (RGB matrix)
platform)
| State | Meaning |
|---|---|
| idle | Claude has finished its turn; waiting for you. |
| working | Claude is actively processing your prompt. |
| thinking | Claude has been responding for a while without firing a |
| tool — usually composing prose. | |
| blocked | Claude needs your input (permission prompt, etc.) |
| compacting | Claude Code is compressing the conversation context. |
| error | A tool call returned an error. |
Plus running counts of subagents and active tasks alongside the state.
Three pieces, in order: firmware → bridge → plugin. Each piece has its own docs; the install walkthrough on the project site sequences them.
The plugin itself installs from inside Claude Code:
/plugin marketplace add sep/cc-status-plugin
/plugin install llmstatus@llmstatus-market
/llmstatus:permit
/llmstatus:help lists every available slash command. Python 3
must be on PATH as the command python (any modern Windows / macOS /
Linux install; on default Ubuntu / Debian, install
python-is-python3).
The plugin also works with GitHub Copilot CLI
(>= 1.0.66), which speaks a Claude-compatible plugin protocol — and
should work with any other agent that implements Claude Code's plugin
architecture; Copilot is the one we test. Same marketplace, same
install commands, from inside copilot:
/plugin marketplace add sep/cc-status-plugin
/plugin install llmstatus@llmstatus-market
Copilot picks up the manifest in .github/plugin/plugin.json, which
routes hook registration to
hooks/copilot-hooks.json — same events,
same emit.py, so Copilot sessions light up the panel exactly like
Claude sessions and can share slots with them.
Copilot-flavored caveats:
- Slot pairing —
CLAUDE_STATUS_SLOT=1 copilotbinds the session to slot 1 at startup, exactly like theclaudeequivalent. The/llmstatus:*commands are surfaced as skills; if your Copilot build doesn't route them, env pairing is the reliable path. /llmstatus:permitknows both hosts — under Claude Code it writespermissions.allowin~/.claude/settings.json; under Copilot it runspermit.py --copilot, writing command-prefix approvals to~/.copilot/permissions-config.jsonand allowlisting the~/.claude-statusstatus dir so file reads stop raising path prompts. Copilot scopes approvals per repo/directory (no global scope), so re-run it once per repo where you use the panel.- No
PostCompactevent — after a context compaction the panel shows compacting until the next tool call or end of turn, then recovers on its own. - Restart sessions after install/update — hooks bind at session
creation, and a resumed session keeps its original registration.
Skills refresh on resume, so
/llmstatus:*commands will respond while the panel stays dark;/restart(or a fresh session) fixes it.
CONTRIBUTING.md— coding conventions, slash command protocol, commit style.MAINTENANCE.md— what the plugin watches for (Claude Code plugin protocol changes, the Python ecosystem) and how to cut a release.bin/— Python sources.emit.pyis the per-event hook;broker.pyis the TCP NDJSON broker;pin.pymanages session-to-slot bindings;permit.pywrites the permission-allowlist entries;stdout-sink.pyis a debugging subscriber.commands/— slash command bodies (one markdown file per command).hooks/hooks.json— Claude Code lifecycle-hook registration. Every event invokesemit.py.hooks/copilot-hooks.json— the same registration in Copilot CLI's native hook format (referenced from.github/plugin/plugin.json). PascalCase event names are load-bearing: they make Copilot deliver Claude-shaped snake_case payloads (hook_event_name,session_id) thatemit.pyparses unchanged.
The bridge ↔ firmware contract — what bytes go over USB serial — lives canonically in the bridge repo: FIRMWARE.md. The plugin publishes events into the broker; the bridge translates them into the wire format. If you're adding a new state or hook, read the spec first.
The plugin's version field in .claude-plugin/plugin.json,
.claude-plugin/marketplace.json, and .github/plugin/plugin.json is
the cache key /plugin update checks against (Claude Code and Copilot
respectively). Bump all three in lockstep before tagging:
# bump all three manifests to the new version
git commit -am "Plugin: bump to vX.Y.Z"
git tag vX.Y.Z
git push --tagsExisting installs pick up the new version on their next
/plugin update.
MIT. © 2026 SEP.