aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-11-10 17:30:26 +0200
committerIgor Soarez <igor@soarez.org>2026-11-10 17:30:26 +0200
commit4cad1009c9d07cdfdab28fe9e073ccb35b54dfd7 (patch)
treef742ceac80ae61d1e893cfd57e080e84410e28cc /README.md
parent87001d7d380173c3d76246d4c0ecdd7b037a619f (diff)
readme: lead with what dingger shows, with real renders of made-up agents; move the details to docs/reference.md; dingger status: fit the agent column to its longest nameHEADmaster
Diffstat (limited to 'README.md')
-rw-r--r--README.md524
1 files changed, 197 insertions, 327 deletions
diff --git a/README.md b/README.md
index 5df0473..8509260 100644
--- a/README.md
+++ b/README.md
@@ -1,342 +1,212 @@
-# 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,
- right-click an idle agent in the full list to mark it unread.
- **⌃⌥⌘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). It grows down
- to the bottom of the screen at most; a longer list scrolls, with the
- summary line kept under it. 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, as written,
- a forgotten draft); ↑↓ or ⌃N ⌃P / ⌃J ⌃K
- select, ↩ or a click jumps, esc closes, ⌃U clears, ⌘U (or a right-click)
- marks the selected idle agent unread. 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. Agents are resumed in their own sessions: pi with `pi --session
- <file>` (reported by the pi extension), Claude Code with `claude --resume
- <id>` (reported by its SessionStart hook, `dingger install claude`), in
- the directory they ran in. A session that never got a message has nothing
- to resume, so its command line is typed instead. Agents that were
- mid-turn are listed. Other panes get the command line that was running typed at the
- prompt, not run: exactly as typed with the zsh hook (`dingger install
- zsh`, then source the file it names from your `.zshrc`), otherwise the
- program's arguments. An agent or program that exits within a minute
- before tmux stops (a shutdown) is still restored.
- When tmux stops, the panel (first row: "↺ Restore 9 sessions · 22
- agents", or "Not now"), the menu (all, or one session at a time), and the
- status line (`↺9`, clickable) offer the restore; it runs through your
- login shell, like a terminal starting tmux, and a notification
- summarizes (full report: `~/.local/state/dingger/restore.log`).
-- All shortcuts live in `~/.config/dingger/config.toml` (menu bar,
- Settings…), reloaded on save.
-- **Jumping to an agent** (any click, a shortcut, search) shows it in a
- terminal window and raises that window. With several terminal windows,
- one already on the agent's session wins (it shows the agent without
- switching), then the focused one, then the one used last. In Ghostty
- (1.3+), the exact window, tab, and split is raised: dingger learns which
- terminal holds each tmux client when it gains focus (tmux's focus hook
- asks Ghostty which terminal is focused; nothing visible changes).
- Other terminals: the app is activated.
-- **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. A pi
- run that ends in an error (after pi's own retries) is still done, but
- says so: "failed" with the error as one readable line (`401: invalid
- x-api-key`), with the "needs attention" sound, and `failed 3m · <why>`
- in red where the wait shows (plain text in the menu; `dingger status`
- too), until you have seen it or it runs again.
-- **Importance**: mark an agent high, normal (the default), or low. Within a
- status, more important agents are listed first, so a jump goes to them
- first; status still comes first (a blocked low agent before a done high
- one). A high agent shows a peach `!` before its name (in a column of its
- own, so names line up); a low agent's name is dimmed, its second line
- says "low", and it makes no sound (it
- still notifies, and waits in line like any other). In pi, type
- `/importance high|normal|low` (no argument: the current level); pi saves
- it with the session, so it comes back after `/reload`, a restart, or a
- restore. For other agents,
- run `dingger importance high|normal|low` in the agent's pane (in Claude
- Code: `! dingger importance high`, which Claude may answer with a model
- turn), or pass `--pane`; it lasts until the agent exits.
-- **Mark as unread**: like in mail, an idle agent (one you have seen) can be
- made done again, to come back to later: right-click it in the panel's
- full list, ⌘U in agent search, `/unread` in pi, or `dingger unread
- [--pane]`. It goes last among done agents of its importance, with no
- sound or notification, and stays done until you come back to it: you
- leave its tmux window and return, or jump to it. Only idle agents can be
- marked: a working, blocked, or done one is left alone. Claude Code has no
- `/unread`, and `! dingger unread` does not work in it: Claude (2.1.311)
- answers a `!` command with a model turn, so the agent is working and the
- mark is refused. Use the panel or search for Claude agents.
-- **What agents cost** (pi): the menu's top line totals what pi agents
- spent today, this week, and this month (`$8.86 today · $457 this week ·
- $8.86 this month`); each agent's line adds what its session spent today
- (at the right edge in the panel and search, after the place in the
- menu), and `dingger status` shows both. Periods start at
- 00:00 UTC (a week on Monday, a month on its 1st). The daemon reads pi's
- session files (`~/.pi/agent/sessions`, and those of running agents
- elsewhere) in a thread of its own every 15 seconds, only what was
- appended since. Each reply counts once, for the session it first appeared
- in: a fork (`/fork`, `pi --fork`) copies its parent's replies into its
- own file, and summing every file would count them again (in one month
- here, $1,024 of $2,991). The totals include agents since closed or
- started over. Amounts are what pi prices each call at (its model
- configuration), counted as pi's footer counts them: replies, compaction
- and branch summaries, and other recorded calls. Claude Code records no
- prices, so it is not counted.
-- **Context use** (pi): how full each agent's context window is, the
- number pi's own footer shows. The panel and search draw it as a small
- meter at the right edge, after the cost (gray; yellow above 70% and red
- above 90%, as in pi; the number on hover); the menu adds it as text
- (`$0.70 · 10%`), and `dingger status` has a CTX column. The extension
- reports it after each message, compaction, model change, and turn; right
- after compacting pi does not know it until the next reply, and neither
- does dingger (no meter). Agents on an older extension show none until
- they reload.
-- **Session names** (pi): every pi session gets a short name of what it is
- about, from the model, after its first run settles; it is refreshed
- every 8 of your messages, and an old unnamed session is named when you
- resume it. A name you give (`/name`, `pi --name`, `/autoname <name>`) is
- never replaced; `/autoname` alone renames now. It runs in every pi
- session (print runs and outside tmux too) and costs one short model call
- per naming; `PI_AUTONAME_MODEL=provider/model-id` picks a cheaper model.
- It replaces the `pi-auto-session-name` package (remove it: `pi remove
- git:github.com/Optomatica/pi-auto-session-name`), and keeps the names
- that package gave automatic.
-- **Forgotten drafts** (pi): an agent with text left in its input box shows
- `✎` before its name wherever you are not looking at it (where you look,
- you may be typing it). Idle agents with one come first among idle agents;
- the compact panel counts them (`✎ 2 drafts`, a click jumps to the
- first); search finds an agent by its draft's words (as written, not
- fuzzily); `dingger status` marks them too. No sound, no notification.
- The extension reads the input box half a second after you stop typing,
- and keeps each session's draft in a file
- (`~/.local/state/dingger/drafts/`) that puts it back in the input box
- when the session comes back: after a crash, a restore, or `pi --session`.
- The draft is kept on the pane as one line (`@dingger_draft`), never in
- dingger's log.
-
-## 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`. Running pi agents load it when
- reloaded (see "Keeping pi agents on the current extension" below).
- It also reports the session's name (`/name`, or pi's own), which is
- the agent's name in dingger whatever the terminal title says: any
- program in the pane can set a title. Agents without it (Claude Code, a
- pi on an older extension) are named from their terminal title.
- Blocked covers a dialog that opens during a run (an extension asking
- before it goes on), until you answer it; one you open while the agent
- is idle changes nothing. A run you cancel (Escape) ends idle, not done,
- with no "finished" notification, even when it settles after you look
- away.
- `dingger install claude` adds a SessionStart hook to
- `~/.claude/settings.json` (keeping everything else; the original is
- backed up to `settings.json.bak-dingger`) that reports only Claude's
- session, for restore; its state comes from screen matching.
-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`, `@dingger_importance`,
-`@dingger_extension`, `@dingger_control`, `@dingger_draft`,
-`@dingger_cost`, `@dingger_context`, `@dingger_error`), so restarting
-dingger loses nothing.
-
-### Keeping pi agents on the current extension
-
-When the daemon starts, it updates the installed pi extension if this
-dingger's differs (only its own file, which says so on its first line).
-Running agents keep the one they loaded, and report its version. While
-some run an old one, the menu bar menu offers **Reload N Agents on an Old
-Extension**; `dingger reload-agents` does the same. Each agent reloads
-(pi's `/reload`) when:
-
-- it is idle (done included: it stays done),
-- it is not on screen, where you may be typing into it,
-- and its input box is empty: the extension checks this itself, at the
- moment it reloads, so nothing you type is lost or sent.
-
-The others reload once they are; the request stays until all are, or the
-daemon restarts. dingger sends no keystrokes: it asks the extension over a
-socket of its own (`~/.local/state/dingger/pi-<pid>-<id>.sock`), and the
-extension runs the reload as a command (`/dingger-reload`, which you can
-also type). A reload also resets pi's other extensions: a pending alarm
-is lost, MCP servers reconnect.
-
-An agent on an extension from before this (it reports no version) takes
-no requests: the menu says how many need `/reload` by hand, once.
-
-The pi extension's reports carry the agent's importance. Other pi
-extensions can follow it on pi's event bus: `dingger:importance` with
-`{ level }`, at session start and on every change; it is saved in the
-session as a custom entry of type `dingger-importance`.
+<h1 align="center">dingger</h1>
+
+<p align="center">
+ <b>Know which of your AI coding agents is waiting for you.</b><br>
+ herdr's agent panel for the tmux you already run, on macOS.
+</p>
+
+![dingger: a floating panel of the agents waiting for you, their counts in the menu bar and the tmux status line](docs/images/hero.png)
+
+You run a dozen coding agents in tmux panes. One finished ten minutes ago,
+one is stuck on a permission prompt, one hit a rate limit, and you are
+reading a diff somewhere else. dingger watches every pane in your tmux
+server, works out which agent runs where and what it is doing, and tells you
+who is waiting for you. Then it takes you there in one keystroke.
+
+- **Every agent at a glance.** It detects 22 agents: Claude Code, Codex, pi,
+ Gemini, opencode, Cursor, GitHub Copilot, Amp, and 14 more.
+- **Waiting agents come to you.** A notification and a sound when an agent
+ finishes or needs your input, and silence when you are already watching it.
+- **One key to the next agent.** <kbd>⌃⌥⌘U</kbd> jumps to the next agent
+ waiting for you, and raises the exact Ghostty window, tab, and split.
+- **Find any agent.** <kbd>⌃⌥⇧⌘U</kbd> opens a Spotlight-style fuzzy search.
+- **Survives a restart.** dingger saves your tmux layout as you work, and
+ after a reboot or crash brings back every session, window, and pane, with
+ each agent resuming its own conversation.
+- **Nothing to migrate.** No new terminal, no new multiplexer: dingger is a
+ client of your tmux server, and keeps its state in tmux options.
+
+## Statuses
+
+dingger tracks [herdr](https://github.com/herdrdev/herdr)'s statuses, with
+herdr's state machine:
+
+| | status | meaning |
+|---|---|---|
+| `×` | **blocked** | needs your input: a permission prompt, a question, a dialog |
+| `✓` | **done** | finished a turn while you were looking elsewhere |
+| `◐` | **working** | a turn is in flight |
+| `○` | **idle** | at its prompt, nothing new since you looked |
+
+A turn that finishes in the tmux window you are looking at stays idle.
+Looking at a window marks its agents seen. Waiting agents are listed blocked
+first, then done, each oldest first. The top one is the next waiting agent:
+where a jump goes.
+
+## The floating panel
+
+![The floating panel: compact, with a restore offer after a restart, and with the full list](docs/images/panel.png)
+
+An always-on-top panel that never takes focus, on every Space and over
+full-screen apps. Click a row to jump to that agent, drag the panel anywhere.
+It can show itself only while agents are waiting, or always, with the full
+list a click away. <kbd>⌃⌥⌘E</kbd> turns it on and off.
+
+Each row says where the agent is (session, window, harness) and for how long
+it has waited. pi agents also show what they spent today and how full their
+context window is.
+
+## Agent search
+
+![Agent search: "edit" fuzzy-matches four agents](docs/images/search.png)
+
+<kbd>⌃⌥⇧⌘U</kbd> from anywhere. Type a few letters of an agent's name,
+session, window, harness, status, or directory, or a word of a draft you left
+in its input box. <kbd>↩</kbd> jumps there. With nothing typed,
+<kbd>↩</kbd> goes to the next waiting agent. Focus goes back
+where it was when you close it.
+
+## The tmux status line
+
+![The status line's counts in a dark and a light terminal theme](docs/images/statusline.png)
+
+The same counts after the clock, in your terminal theme's own colors. Click
+them to jump to the next waiting agent.
+
+The menu bar shows the counts too. Its menu lists every agent and what pi
+agents spent today, this week, and this month.
+
+## Restore after a restart
+
+dingger saves your tmux layout as it changes: sessions, windows, panes,
+layouts, directories, and what runs in each pane. When tmux stops (a
+restart, a crash, `kill-server`), the panel, the menu, and the status line
+offer to bring it all back. Agents resume their own sessions: pi with
+`pi --session`, Claude Code with `claude --resume`. Other programs get their
+command line typed at the prompt but not run, so nothing starts behind
+your back.
+
+## More for pi agents
+
+With dingger's pi extension (`dingger install pi`):
+
+- **Exact states.** The extension reports state directly, including dialogs
+ during a run, cancelled runs, and failed ones: "failed 3m · 429: rate limit
+ exceeded".
+- **Session names.** Every session gets a short name of what it is about,
+ from the model, kept current as the work moves on. A name you give it with
+ `/name` is never replaced.
+- **What agents cost.** Today, this week, and this month, counted the way
+ pi's footer counts, with forked sessions counted once.
+- **Context use.** A meter per agent that turns yellow above 70% and red
+ above 90%.
+- **Forgotten drafts.** `✎` marks an agent with text left in its input box,
+ and restore puts the draft back.
+- **Importance.** `/importance high` puts an agent ahead of the others with
+ its status and marks it `!`. A low agent is quiet and dimmed.
+- **Mark as unread.** `/unread` turns an agent done again, to come back to.
+- **Kept current.** When dingger updates its extension, the menu offers to
+ reload the agents on the old one. Each reloads only when it is idle, out of
+ sight, and has an empty input box.
+
+Claude Code and the other agents get the rest: detection from the screen,
+statuses, notifications, jumping, search, and restore.
-## Usage
+## Install
+
+dingger runs on macOS, with tmux. You need Rust, and Xcode's command line
+tools for the menu bar app.
```bash
-dingger watch # the daemon (run by launchd, see below)
-dingger status # agents in priority order, with cost and context use
-dingger focus [--pane ID] # jump to an agent (default: the top waiter)
-dingger importance [LEVEL] # an agent's importance: high, normal, low
-dingger unread [--pane ID] # mark an idle agent unread (done again)
-dingger reload-agents # reload pi agents on an old extension
-dingger install pi # install the pi state-reporting extension
-dingger install claude # report Claude Code sessions, for restore
-dingger install zsh # record typed command lines, for restore
-dingger manifests update # refresh manifests from herdr's catalog
+# the daemon and CLI
+cargo build --release
+cp target/release/dingger ~/.local/bin/dingger
+codesign --force --sign - ~/.local/bin/dingger # sign the copy, or macOS kills it
+
+# the menu bar app, the panel, and search
+swiftc -O -o target/dingger-menu menubar/*.swift # bundle it as ~/Applications/dingger.app
```
-## Install
+1. **Start at login.** Adjust the user path in
+ `packaging/launchd/dev.dingger.{dingger,menubar}.plist`, copy both to
+ `~/Library/LaunchAgents/`, then run
+ `launchctl bootstrap gui/$(id -u) <plist>` for each.
+2. **Add the status line.** At the end of your tmux config, add the
+ following line, then reload with `tmux source-file ~/.tmux.conf`:
+
+ ```tmux
+ source-file /path/to/dingger/packaging/tmux/dingger.tmux
+ ```
+
+ It replaces `status-right`, turns on `focus-events`, and adds hooks
+ that tell dingger where you are looking. Clicking the counts needs
+ `set -g mouse on`.
+3. **Connect your agents** (optional, recommended):
+
+ ```bash
+ dingger install pi # pi: exact states, names, cost, drafts, restore
+ dingger install claude # Claude Code: its session, for restore
+ dingger install zsh # your exact command lines, for restore
+ ```
+
+Shortcuts live in `~/.config/dingger/config.toml` (Settings… in the menu).
+[Install in detail](docs/reference.md#install-in-detail) covers what each
+step changes.
+
+## Usage
```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
+dingger status # every agent, in the order they wait
+dingger focus # jump to the next waiting agent
+dingger restore --dry-run # what a restore would bring back
+dingger importance high # in an agent's pane: list it first
+dingger unread # in an agent's pane: mark it unread
+dingger reload-agents # move pi agents onto the current extension
+dingger manifests update # refresh detection rules from herdr's catalog
+```
+
+`dingger status`, abridged:
+
+```text
+STATUS WHERE AGENT COST CTX NAME
+blocked infra · certs pi $0.42 18% ! Rotate staging TLS certificates
+blocked api · db claude Approve migration for the orders table
+failed web · deps pi $0.97 54% Upgrade to React 19 · 429: rate limit exceeded
+done api · tests pi $1.84 38% Fix flaky retry test in the HTTP client
+working api · perf pi $3.10 76% Profile the slow search endpoint
+idle cli · config pi $2.15 61% ✎ Refactor the config loader
+
+spent: $23.40 today · $187.15 this week · $612.80 this month (UTC)
```
-- 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: you add the snippet yourself; no `dingger install` command does it.
- Put this line at the end of your tmux config (`~/.tmux.conf`, or a file
- it sources):
-
- ```tmux
- source-file /path/to/dingger/packaging/tmux/dingger.tmux
- ```
-
- Then reload: `tmux source-file ~/.tmux.conf`. The snippet:
- - **replaces** `status-right` with the restore offer, `%d-%b %H:%M`, and
- the agent counts. Your own `status-right` is lost, so put the line after
- it, or edit the snippet's `status-right` to keep what you had;
- - turns on `focus-events`, which raising the right Ghostty window needs;
- - adds hooks that report focus and navigation to dingger. They use index
- `[77]`, so they keep your own hooks on the same events, and reloading
- does not duplicate them;
- - binds clicks on the right of the status line, which needs
- `set -g mouse on`.
-
- The line sources the snippet from the repo, so a changed snippet applies
- at the next reload.
-
-dingger does not need tmux running first; it attaches when a server appears.
+## How it works
+
+A daemon (`dingger watch`) polls your tmux server every second and wakes at
+once on agent reports and tmux focus hooks. It decides each pane's status
+from the agent's own reports where an integration exists, and otherwise
+from the screen, through herdr's detection rules. It writes everything to
+tmux pane options, so the menu bar app, the panel, and the status line are
+stateless readers, and restarting dingger loses nothing. dingger owns no
+terminals, and does not need tmux to be running when it starts.
+
+The [reference](docs/reference.md) has the details: every feature's exact
+behavior, where state comes from, and how the pi extension is kept current.
## Testing
```bash
-cargo test # engine tests + herdr state-machine tests
-tests/e2e.sh # end to end against an isolated tmux server
-tests/pi-live.sh <pi> # a real pi binary against dingger (new pi releases)
-tests/shutdown.sh [dingger] [rounds] # restore after the ways an OS stops tmux
+cargo test # engine and state-machine tests
+tests/e2e.sh # end to end, on a tmux server of its own
+tests/pi-live.sh <pi> # a real pi release, with a fake model
+tests/shutdown.sh # restore after the ways an OS stops tmux
```
-`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.
-
-`tests/pi-live.sh <pi binary>` checks a real pi release on a tmux server and
-daemon of its own, with a fake local model (`tests/fake-model.py`) and its
-own pi agent directory: no network, no credentials, your pi untouched. It
-covers identification, the extension's state and session reports, a turn,
-`/reload`, an extension's blocked event, dialogs (blocked during a run,
-nothing while idle), a cancelled run (idle, not done), a failed run (done,
-with why; cleared when seen or run again), names (the session's, not the
-terminal title's; named after the first run, renamed every 8 messages,
-yours kept, print runs and resumed old sessions named), screen detection
-without the
-extension, restore (after tmux and its real pis are stopped as a restart
-stops them: SIGTERM to all at once, then SIGKILL), reloading an agent on an old extension (and refusing
-while busy or with a draft), drafts (reported, cleared, kept across a
-crash and put back by restore), cost (priced replies, counted once across
-a real `pi --fork`), context use (pi's footer's number, through a
-compaction), and exit. To try a release without installing it:
-`npm install --prefix /tmp/pi-next @earendil-works/pi-coding-agent@<version>`
-(through `sfw`), then `tests/pi-live.sh /tmp/pi-next/node_modules/.bin/pi`.
-
-`tests/shutdown.sh` stops tmux the ways a restart or logout can, each on
-servers, a daemon and a state directory of its own, with fake agents: SIGTERM
-to tmux; to the agents first; the daemon first; the terminal quitting first;
-and, for 20 rounds each by default, SIGTERM and SIGKILL to everything at once
-(tmux, its panes, the terminal's client, the daemon) in a random order at a
-random moment. After each it logs in again (a fresh daemon if its own was
-stopped; a terminal running `tmux new-session -As 0`) and checks the old
-layout is offered and restoring brings back every session, window, pane,
-directory, agent and command.
-
-## 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; `tests/pi-live.sh`: a real pi.
+None of them touch your tmux server, your agents, or your pi setup.
+[Testing](docs/reference.md#testing) lists what each covers. The README's
+images are real renders of made-up agents: `docs/screenshots.sh` makes them.
+
+## Credits
+
+dingger's detection engine and agent rules come from
+[herdr](https://github.com/herdrdev/herdr) (Apache-2.0), and so do its
+statuses, state machine, colors, and sounds.