1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
# 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 <entry.js>` 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 <name>`)
- `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.
|