diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 149 |
1 files changed, 91 insertions, 58 deletions
@@ -1,74 +1,107 @@ # 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. +macOS agent watcher for tmux panes: herdr's agent panel, without herdr. +dingger watches every pane in your tmux server, detects which AI coding agent +runs in each (claude, codex, pi, gemini, and 19 others), tracks herdr's five +statuses, and tells you which agents are waiting for you: in the menu bar, in +the tmux status line, and with a notification and a sound. + +dingger owns no terminals. It is a client of your tmux server. The detection +engine and rule manifests are extracted from +[herdr](https://github.com/herdrdev/herdr) (Apache-2.0). + +## Statuses (herdr's, verbatim) + +| status | meaning | symbol | color | priority | +|---------|--------------------------------------------------|--------|--------|----------| +| blocked | needs your input | `×` | red | 4 | +| done | finished a turn while you were looking elsewhere | `✓` | teal | 3 | +| working | turn in flight | `◐` | yellow | 2 | +| idle | at its prompt, nothing new since you looked | `○` | green | 1 | + +The state machine is herdr's `apply_pane_state_change` and +`mark_active_tab_seen`, implemented as one pure function (`watcher::advance`) +and unit-tested against herdr's own test cases: + +- a turn that finishes while you look at another window becomes **done**; +- a turn that finishes in the window you are looking at stays **idle**; +- looking at a window (a tmux window is herdr's tab) turns its done agents + **idle**, as does jumping to one from the menu bar or status line; +- an agent seen for the first time is **idle**, not done. + +dingger watches tmux focus through tmux hooks, so attendance updates the +moment you switch windows; agent reports apply in order, so a turn shorter +than a poll is still a completion. + +## Surfaces + +- **Menu bar** (`menubar/main.swift`): `×1 ✓3 ◐2` in herdr's colors; the + menu lists every agent in priority order, oldest waiting first. Clicking a + row jumps to that pane. +- **tmux status line** (`packaging/tmux/dingger.tmux`): the same summary after + the clock. Clicking it jumps to the top waiting agent (the menu's top row), + skipping the window you already look at. +- **Notifications**: "needs attention" (blocked) and "finished" (done), with + herdr's sounds; silent for completions you are already watching. + +## Where state comes from + +1. **Agent hooks** (authoritative): `dingger install pi` installs a pi + extension that reports working/blocked/idle over + `~/.local/state/dingger/agent.sock`. Restart running pi agents to load it. +2. **Screen matching** (fallback): `capture-pane` text through herdr's + manifests, for agents without hooks. + +All per-pane state lives in tmux pane options (`@agent_state`, +`@dingger_since`, `@dingger_hook`), so restarting dingger loses nothing. ## 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 watch # the daemon (run by launchd, see below) +dingger status # agents in priority order +dingger focus [--pane ID] # jump to an agent (default: the top waiter) +dingger install pi # install the pi state-reporting extension +dingger manifests update # refresh manifests from herdr's 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. +## Install -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 +```bash +cargo build --release +cp target/release/dingger ~/.local/bin/dingger # do not strip: invalidates +codesign --force --sign - ~/.local/bin/dingger # the signature, macOS kills it +swiftc -O -o target/dingger-menu menubar/main.swift # menu bar, bundled as + # ~/Applications/dingger.app +``` -Early scaffold. Roadmap: menu bar UI (Swift shell over the daemon), configurable -notification policy. +- launchd agents (start at login, restart on exit): + `packaging/launchd/dev.dingger.{dingger,menubar}.plist`; adjust the user + path, copy to `~/Library/LaunchAgents/`, `launchctl bootstrap gui/$(id -u) <plist>`. +- tmux: `source-file /path/to/dingger/packaging/tmux/dingger.tmux` (needs + `set -g mouse on` for the click). -### Running as a login agent (launchd) +dingger does not need tmux running first; it attaches when a server appears. -`packaging/launchd/dev.dingger.dingger.plist` runs `dingger watch` at login -with auto-restart; adjust the user path, then: +## Testing ```bash -launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.dingger.dingger.plist +cargo test # engine tests + herdr state-machine tests +tests/e2e.sh # end to end against an isolated tmux server ``` -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. +`tests/e2e.sh` starts its own tmux server, its own daemon (polling only every +30 s, so everything asserted must arrive through events), fake agents driven +over the hook protocol, and a real tmux client whose focus it controls. It +asserts every status transition, attendance by navigation, both click paths +(including the real menu bar binary), the status-line segment, latency with a +deliberately slow notifier, and state surviving a daemon restart. It never +touches your tmux server. + +## Layout + +- `crates/detect-core`: the detection engine and 22 manifests, from herdr. +- `crates/dinggerd`: the `dingger` binary (watcher, hooks, focus, CLI). +- `menubar/`: the menu bar app. +- `packaging/`: launchd agents and the tmux snippet. +- `tests/e2e.sh`: the end-to-end suite. |
