Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,32 @@ makes a reconnect safe: the cursor, not the notification, is the source of truth
missed broadcast costs nothing, and a 409 means catch up in full instead. A read that
fails leaves the cursor where it was and is retried on a doubling backoff, so a change
isn't lost with the notification that announced it, and a subscription that closes without
the watch being interrupted is an error rather than a quiet exit.
the watch being interrupted is an error rather than a quiet exit. `ready` waits for that
retry too (`catchUp`/`retryUnread`/`readyOnceCaughtUp`): a catch-up that left a box behind
owes its ready until the box is read, and a drop in between cancels the debt — the
reconnect's catch-up announces its own. The look at the transition queue and the ready
announcement are one critical section with `noteConnection`'s queueing, which is what keeps
a drop from landing between them.

New mail is a watch event, not a flag: every added and updated line carries `"new":
true|false` and `--events new` selects the true ones (`internal/cmd/watch_new.go`). New is
unseen, not muted, and `active_at` later than the watch's record of the thread — or than
the watch's start, on HEY's clock (`serverNow`), for a thread it has no record of — and
every posting the watch reads is recorded, in every box and whatever `--events` or `--box`
says (`--box` picks the boxes whose changes are reported; every box is followed), each
posting recorded as soon as it is classified. The
start is the Date header translated back to when the request was made (mail that lands while
the server answers is later than the start), it is taken before the box list, and each
box's cursor starts no later than it (`noLaterThan`): the server bakes the box's last posting
activity into the cursor, so mail that landed in between would otherwise sit behind the
cursor, read by nothing. That
is HEY's semantics and state across events, so the CLI decides it once; what to do about
it is the reader's. A 409 skip-ahead sets that box's floor at the cursor it skipped to
(`newMail.skippedTo`): activity at or before it is never new there, known thread or not,
because the watch never read the gap. `resync` is an event of its own — reported by default,
left out by `--events new` — so a script for new mail never runs on one. The Omarchy bar plugin toasts from those lines itself (app-name, glyph,
click-to-focus and the replace-not-stack id all live in the plugin), and nothing
desktop-shaped lives in `watch*.go`.

The TUI's mail list follows the same channel and wants less from it. `internal/cmd/tui_watch.go`
subscribes and relays the changed box IDs down a channel; `internal/tui/live.go` defines
Expand Down
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,16 +388,29 @@ hey watch # follow every box, a line of JSON per c
hey watch --box imbox --events added # only new postings in the Imbox
hey watch --box imbox --exit-on-first # block until something lands, then exit
hey watch --since 2026-08-18T09:00:00Z # catch up from a time first, then follow
hey watch --run-async 'notify-send "New mail in $HEY_BOX_KIND"'
hey watch --box imbox --events new # new mail only: unseen, unmuted, active since the watch began
hey watch --box imbox --events new --exit-on-first # block until new mail
hey watch --box imbox --events new --run-async 'notify-send -a HEY "New mail in HEY"'
hey watch --run-sync ./triage.sh # one at a time, waiting for each
```

Runs until interrupted, printing changes as they happen, one line each:

```json
{"change":"added","at":"2026-08-18T09:14:22.031Z","box":{"id":24088,"kind":"imbox","name":"Imbox"},"posting_id":98765,"thread_id":54321,"posting":{}}
{"change":"added","at":"2026-08-18T09:14:22.031Z","box":{"id":24088,"kind":"imbox","name":"Imbox"},"posting_id":98765,"thread_id":54321,"new":true,"posting":{}}
```

Every `added` and `updated` line says whether the posting is new mail: unseen, not muted,
and active since the watch last saw the thread — or since the watch began, for a thread it
has not seen, so a box's backlog is never new. Reading, muting or moving a thread is not new
activity; a reply on a known thread is. `--events new` selects the new ones, alone or in a
union with `added`, `updated`, `deleted` and `resync` — the default is everything but `new`,
and `new` alone leaves a `resync` out, so a script for new mail never runs on one. `--box`
picks the boxes whose changes are reported; every box is followed regardless, so what is new
is judged across all of them — a reply in The Feed and then a move into the Imbox is not. The
one-liner above is what any desktop does with it; on Omarchy the bar plugin reads the same
lines and sends one batched, replacing toast instead.

A change can drive a command instead of being printed, and there's a choice to make
between two behaviours — pass one or the other, not both. `--run-async` spawns the
command per change and moves on, so a slow one never holds up the watch and two can
Expand All @@ -406,7 +419,8 @@ slow one delays the next.

Both hand the JSON to the command on its stdin, and the same fields as `HEY_CHANGE`,
`HEY_AT`, `HEY_BOX_ID`, `HEY_BOX_KIND`, `HEY_BOX_NAME`, `HEY_POSTING_ID` and
`HEY_THREAD_ID`. Both also take over stdout, so the JSON isn't printed as well.
`HEY_THREAD_ID`, with `HEY_NEW=1` for new mail and `HEY_NEW=0` otherwise. Both also take over
stdout, so the JSON isn't printed as well.

### Calendars

Expand Down Expand Up @@ -491,21 +505,29 @@ read from `~/.local/state/omarchy/current/theme/`, and restyles live when you ru
— an explicitly chosen file is trusted as written — or `NO_COLOR=1` to turn color off.

```bash
yay -S hey-cli # hey-cli is on the AUR
omarchy pkg aur add hey-cli # hey-cli is on the AUR
omarchy plugin add https://github.com/basecamp/omarchy-hey-plugin.git --enable
hey setup omarchy # install into the desktop
hey setup omarchy --notify # also toast new Imbox mail (--no-notify turns it off)
hey setup omarchy --notify # toast new mail from the bar plugin (--no-notify turns it off)
hey setup omarchy --remove # take it all out again
```

Setup installs a `HEY TUI` launcher entry, a `HEY` row in the SUPER+SPACE menu, a bar
indicator that lights when the Imbox has unread mail (no count, by design), and a
The bar gets the [HEY plugin](https://github.com/basecamp/omarchy-hey-plugin): the HEY
logo lights when the Imbox has unseen mail and opens a panel of recent threads. hey-cli is
its engine — the plugin reads the Imbox with `hey box imbox` and runs `hey watch` so the
bar is live: a thread you archive in the TUI, on your phone or in the web app leaves the
panel within a second, and after a disconnect the watch catches up from where it left off.

Setup installs a `HEY TUI` launcher entry, a `HEY` row in the SUPER+SPACE menu, and a
`hey.toml.tpl` theme template so theme authors can tune the overlay. It prints the
`bindings.lua` snippet for a keybinding rather than editing your file. Omarchy's shipped
HEY web app, its SUPER+SHIFT+E binding and the mailto handler are left untouched.

`--notify` turns on new-mail toasts, off by default: the bar indicator's poll also sends
at most one notification per interval — `Sender — Subject` for one new thread, `N new in
Imbox` for more — replacing the previous toast rather than stacking, and clicking it
`--notify` turns on new-mail toasts, off by default, by flipping the plugin's `notify`
setting (the panel's toggle and `omarchy bar set 37signals.hey notify true --json` do the
same). The plugin runs `hey watch` and toasts its new-mail lines for the Imbox itself — at
most one notification per burst of changes, `Sender — Subject` for one new thread, `N new
in Imbox` for more, replacing the previous toast rather than stacking, and clicking it
focuses the TUI. Omarchy's notification silencing (SUPER+CTRL+comma) mutes them like any
other app. See [docs/omarchy.md](docs/omarchy.md) for the details and what is planned next.

Expand Down
Loading
Loading