Overview
lil agents is an open-source native macOS app with a menu-bar view and floating overlay for Claude Code and Codex CLI sessions. It turns CLI lifecycle events into three visible states: working, idle, and waiting for approval. I can see that status without checking the terminal, then select a session row to return to the tab, pane, or split that owns it.
Parallel agent sessions are useful, but their state quickly gets scattered across terminal windows, tabs, and panes. I didn’t want to check every terminal to find the session that finished or needed a permission decision. The app shows that state globally and gets me back to the right terminal target in one action.
Architecture
Native macOS surface: A Swift 6.2 SwiftPM executable targeting macOS 26. SwiftUI owns the MenuBarExtra and Settings scenes, while AppKit supplies the non-activating NSPanel used by the floating overlay. The panel stays above other Spaces and full-screen apps without taking key focus from the terminal underneath.
Hooks, listener, and UI: The app installs lifecycle hooks for both CLIs into their existing per-user JSON configuration. The generated forwarder reads the hook’s JSON from stdin, adds the tool, event, controlling TTY, and best-effort terminal metadata, then sends it to 127.0.0.1:8787/event. An embedded Network.framework NWListener is pinned to 127.0.0.1. It accepts only POST /event, checks the per-install bearer token, decodes the event, and hands it to the main-actor session store. That store maps lifecycle events to working, idle, or waitingApproval, then drives the menu bar and overlay.
Terminal targeting: A session row carries the terminal identity and TTY captured at hook time. A single jump router dispatches the action off the main thread to the appropriate adapter:
- iTerm2 and Terminal.app: AppleScript matches the controlling TTY, selects the right session or tab, raises its window, and activates the app.
- WezTerm:
wezterm cli activate-panefocuses the captured pane, with a TTY-based lookup when an ID is unavailable. - tmux: The app selects the session’s pane and window, switches the attached client when needed, and raises the detected host terminal.
- Ghostty: AppleScript searches windows, tabs, and terminals by TTY first, then by working directory. Activating the app is the safe fallback.
The overlay does more than report that work is waiting. It preserves enough terminal context to return to that work.
Config safety: Hook installation is idempotent and self-healing. The installer merges existing Claude Code and Codex CLI hook entries instead of replacing them. It removes stale entries that reference an older forwarder, adds the canonical command once, and leaves unrelated hooks untouched. Claude Code’s original settings are backed up once, and writes are atomic. The test suite covers double-install de-duplication, preservation of foreign hooks during install and uninstall, and repair of a stale forwarder entry.
Privacy boundary: Session events use loopback-only transport, with no path for exporting analytics or telemetry. The listener stores its bearer token in a per-install file with restrictive permissions. Separate Claude and Codex usage polling requires an explicit opt-in and is disabled by default, so it is not part of the lifecycle-event path.
Distribution
Sparkle owns the in-app updater, with automatic checks enabled through the app’s feed configuration. Tag-triggered GitHub Actions CI imports a Developer ID certificate, builds and verifies the app signature, submits the app and DMG to Apple’s notarization service, staples and validates the DMG, signs the Sparkle update archive, updates the appcast, and publishes both the .dmg and update .zip as release artifacts.
The full source is available on GitHub, so the hook contract, local listener, terminal adapters, and release tooling are inspectable and reusable.
