# dingger macOS agent watcher for tmux panes. dingger watches every pane in your tmux server, detects which AI coding agent is running in each one (claude, codex, pi, gemini, and 19 others), tracks its state (idle / working / blocked), and sends a macOS notification — with sound — when an agent needs attention or finishes. dingger owns no terminals. It is a client of your tmux server: `capture-pane` for screen text, `#{pane_title}` for OSC titles, and `#{pane_pid}` plus a `ps` tree walk for agent identity. The detection engine and rule manifests are extracted from [herdr](https://github.com/herdrdev/herdr) (Apache-2.0), so detection quality matches herdr's screen-matching engine. herdr's manifests use the same terminal-screen regions (`capture-pane` output maps 1:1); OSC progress sequences are not re-exposed by tmux (a small confidence loss on a few rules). Agents launched as bare `node ` scripts (rather than via an install shim) are not identified yet. ## Usage ```bash dingger watch # run the watcher; notify on transitions dingger status # table of panes, agents, states dingger manifests update # refresh manifests from herdr's published catalog ``` `dingger status` reads state written back into tmux as the per-pane user option `@agent_state`, so it works without any daemon IPC. Render it directly in your status line with `#{:@agent_state}` in `status-format` / `pane-border-status`. `dingger manifests update` stores refreshed manifests from herdr's published catalog; restart `dingger watch` to pick them up. Environment: - `DINGGER_TMUX_SOCKET` — target a named tmux server socket (`tmux -L `) - `DINGGER_CONFIG_DIR` / `DINGGER_STATE_DIR` — override/remote manifest locations (default `~/.config/dingger` / `~/.local/state/dingger`) ## Notification policy herdr's transition policy: notify on any transition to `blocked` ("needs attention", `request.mp3`) and on `working`/`blocked` → `idle` ("finished", `done.mp3`), with no notification for `unknown` panes. One deliberate difference from herdr: a pane on the active window of an attached client is suppressed entirely (herdr still plays the blocker sound there). Viewer screens such as Claude's transcript never change state. ## Layout - `crates/detect-core` — the detection engine (manifest compiler, region extractors, priority arbitration) and 22 bundled manifests, extracted from herdr's `src/detect/`. Pure text-in → state-out. - `crates/dinggerd` — the `dingger` binary: tmux watcher, notification policy, sounds, CLI. - `sounds/` — herdr's bundled notification sounds. ## Status Early scaffold. Roadmap: menu bar UI (Swift shell over the daemon), configurable notification policy. ### Running as a login agent (launchd) `packaging/launchd/dev.dingger.dingger.plist` runs `dingger watch` at login with auto-restart; adjust the user path, then: ```bash launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.dingger.dingger.plist ``` dingger does not require tmux to be running first — it waits for a server and attaches when one appears. All tmux invocations use `-u` (forced UTF-8): under launchd's C locale, tmux mangles tab-separated `-F` output into underscores, which would silently hide every pane.