A tiny macOS menu-bar app (with an optional floating traffic light) that shows what your AI coding agent is doing at a glance — like the physical traffic light some people stick on their monitor, but in software.
Works with both Cursor and Claude Code, and aggregates across all of them: if any session is working the light is orange.
🌐 Website & live demo: https://agentlights.com
⬇︎ Download the latest release (.dmg)
Universal (Apple Silicon + Intel), signed with a Developer ID, notarized, and stapled — it opens with no Gatekeeper warning. macOS 13 or later. You can also grab it from agentlights.com.
| Color | State | Meaning |
|---|---|---|
| 🟢 Green | idle |
Done — the agent finished and is waiting for your input |
| 🟠 Orange | working |
A turn is running (thinking or using tools) |
| 🔴 Red | waiting / error |
The agent needs you (a question/permission) or the last turn errored |
| ⚪️ Gray | unknown |
No recent activity recorded yet |
It aggregates across all open Cursor/Claude windows and conversations: if any conversation is actively working, the light is orange; otherwise it reflects the most recent finished turn.
Both Cursor (hooks) and Claude Code
(hooks) can run scripts on agent
lifecycle events. A single, observe-only hook script records the current state
per conversation/session into
~/.cursor/cursor-traffic-light/state/<id>.json:
| Tool | working | idle (done) | waiting / error |
|---|---|---|---|
| Cursor | beforeSubmitPrompt, tool events |
stop (completed), sessionEnd |
shell/MCP awaiting approval → red; stop (error) |
| Claude Code | UserPromptSubmit, PreToolUse, PostToolUse |
Stop, SessionEnd |
Notification (needs you) → red; StopFailure |
The menu-bar app watches that directory and updates the indicator. The hook
never blocks, denies, or rewrites anything the agent does — it only records
state (and emits no stdout for Claude, so nothing is injected into its context).
A working entry older than 15 minutes is treated as stale (e.g. the window was
closed) and ignored.
Cursor / Claude event ──hook──▶ ~/.cursor/cursor-traffic-light/state/*.json ──watch──▶ menu bar + overlay
Scripts/build_app.sh
open dist/AgentLights.appbuild_app.sh produces a universal binary (arm64 + x86_64). On launch the
app installs the hooks automatically (idempotent), merging into any existing
~/.cursor/hooks.json and — when Claude Code is detected (~/.claude exists) —
~/.claude/settings.json, rather than overwriting them. Cursor reloads
hooks.json on save, so it starts reporting within seconds. Claude Code picks
up the new hooks on the next session.
For development you can also just run the executable directly:
swift runTo build a signed, notarized .dmg for distribution:
SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
NOTARY_PROFILE="your-notary-profile" Scripts/make_dmg.shClick the menu-bar dot for:
- Show Floating Light (
⌘L) — draggable, always-on-top traffic light(s). Each light's position and visibility are remembered. - Floating Light Layout — choose how the overlay is shown:
- Combined — a single light showing the overall state.
- One per Cursor Window — a separate, labeled traffic light for each active window/workspace, so you can watch every worker at once.
- Row of dots — a compact strip with one colored dot per agent.
- One Light per Tab — split a window into one light per conversation (tab).
- Orientation — arrange lights vertically or horizontally.
- Light Size — resize the floating light(s), or pick a preset (Small … Huge).
- Settings… (
⌘,) — pick the layout, drag the Size slider, toggle labels, drag to reorder your lights, and see detected windows/tabs. - Launch at Login — register/unregister via
SMAppService. - Check for Updates… — compares your version against the latest release.
- Install / Reinstall Hooks — re-run the hook installer if needed.
- Quit.
In per-window mode, windows are grouped by workspace (the folder open in the Cursor/Claude window). A window appears once it has recent activity and drops off after two hours of inactivity.
| Path | Purpose |
|---|---|
Sources/AgentLights/AgentState.swift |
State enum + colors + labels. |
StatusStore.swift |
Watches the state dir, computes overall + per-window/tab state. |
Settings.swift |
Persisted settings (AppSettings) + Settings window. |
MenuBarController.swift |
Menu-bar dot + menu (layout, settings). |
TrafficLightOverlay.swift |
Floating light(s): combined, per window, or row. |
HookInstaller.swift |
Installs/merges the Cursor + Claude hooks. |
Updater.swift |
"Check for Updates" against the published manifest. |
AppDelegate.swift / main.swift |
Agent (LSUIElement) entry point. |
hooks/cursor-traffic-light.sh |
The shared observe-only hook script (Cursor + Claude). |
- The hook depends on
jq(preinstalled on most dev Macs;brew install jqotherwise). Without it the script still records a coarse state. - To remove: delete
~/.cursor/hooks/cursor-traffic-light.sh, remove the matching entries from~/.cursor/hooks.jsonand~/.claude/settings.json, and drop the app.
Created by Daniel Leonti.
MIT © 2026 Daniel Leonti.