all repos — dingger @ 0f06b0b15d6b064703fa1166fb8e7ceda209aaa8

Watches the AI coding agents in your tmux panes on macOS and tells you which need you, from the menu bar, a floating panel, the status line and notifications; jumps to them, and restores tmux sessions and agents after a crash

README.md (view raw)

 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
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
# dingger

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.
- **Floating panel** (`menubar/panel.swift`): an always-on-top HUD that never
  takes focus, on every Space and over full-screen apps. It lists the agents
  waiting for you (blocked, done) and summarizes the rest
  (`◐ 2 working · ○ 15 idle`); click a row to jump there, drag to move it.
  **⌃⌥⌘E** (or "Show Panel" in the menu) turns it on or off; off stays off
  until you turn it back on. While on, "Panel Shows" picks *Only When Agents
  Are Waiting* (default: it hides itself while nothing waits; turning it on
  then shows it for 3 seconds so you see it is armed; compact) or *Always*
  (with a "Show all / Show less" toggle for the full list). The panel is
  always dark, in herdr's palette, so it reads the same over any desktop.
- **Jump shortcut**: **⌃⌥⌘U** jumps to the top waiting agent from anywhere,
  like clicking the tmux status line.
- **Agent search**: **⌃⌥⇧⌘U** opens a Spotlight-style search over every
  agent. Type to fuzzy-filter (each word must match, letters in order, the
  name, session, window, harness, status, or directory); ↑↓ or ⌃N ⌃P / ⌃J ⌃K
  select, ↩ or a click jumps, esc closes, ⌃U clears. With nothing typed,
  agents are in the menu's order, so ↩ jumps to the top waiting agent. It
  takes the keyboard without activating dingger, so focus returns where it
  was.
- **Restore**: dingger saves your tmux layout as it changes (sessions,
  windows, panes, layouts, directories) under
  `~/.local/state/dingger/snapshots/`. After a crash, shutdown, or
  `kill-server`, `dingger restore` brings back what is missing (`--dry-run`
  to preview, `--list` for saved snapshots, `--session` for some). Sessions
  that exist are left alone, except a bare one of the same name (what a
  terminal creates on startup), which is replaced with its clients moved
  over.
- All shortcuts live in `~/.config/dingger/config.toml` (menu bar,
  Settings…), reloaded on save.
- **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                 # 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
```

## Install

```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/*.swift    # menu bar + panel, bundled
                                                    # as ~/Applications/dingger.app
```

- 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).

dingger does not need tmux running first; it attaches when a server appears.

## Testing

```bash
cargo test           # engine tests + herdr state-machine tests
tests/e2e.sh         # end to end against an isolated tmux server
```

`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 floating panel's visibility and
layout rules and content (compact, full, toggle), agent search (matching,
ranking, selection), 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 (`model.swift` rows and colors, `panel.swift`
  the floating panel, `hotkey.swift` the global shortcut and settings).
- `packaging/`: launchd agents and the tmux snippet.
- `tests/e2e.sh`: the end-to-end suite.