Skip to content

pszypowicz/pager

Repository files navigation

Pager

Beta: Pager is pre-1.0. Backward compatibility is not guaranteed until version 1.0.0 is reached.

A tiny macOS agent that delivers notifications you can click to jump back to the exact place that paged you, and clear the moment you return - driven from a shell one-liner or a tmux hook.

It exists because osascript display notification is a dead end: notifications are attributed to Script Editor (so clicking lands you there), and there is no way to withdraw one once delivered. Pager owns its own identity and a real click handler, and exposes a remove command so a page disappears as soon as you are looking at what it was about.

What it does

  • Posts a notification under its own identity (its icon, not Script Editor's).
  • Click to focus: a page carries a tmux session and window id; clicking it switches the most recently used client to that window and brings the terminal (Ghostty) forward.
  • Programmatic dismissal: pager remove --key <id> withdraws a delivered page, so a tmux focus hook can clear it when you return to its window.
  • Per-page thumbnails: --image attaches an image so different callers can look distinct without changing the app icon.

The daemon runs as a login-item agent (no Dock icon, no menu bar). A single binary is both the agent and the CLI.

Requirements

  • macOS 15+
  • tmux and Ghostty for the click-to-focus action (Pager still posts and clears without them)

Install

brew install pszypowicz/tap/pager

The cask symlinks the pager CLI onto your PATH and registers the agent to launch at login. On first launch, allow notifications when macOS asks.

Usage

pager post   --key <id> [--title T] [--subtitle S] [--body B]
             [--sound NAME] [--image PATH] [--session-id SID] [--window-id WID]
pager remove --key <id>
pager --version

--key is the notification's identity and its dedup/removal key. Pass the tmux #{window_id}: it is server-unique and stable across renumber-windows, so a page still matches its window at dismiss time.

Post a page for a background tmux window:

pager post --key @7 --title "Attention" --subtitle "work / window 3" \
  --body "waiting for input" --sound Funk --session-id '$2' --window-id @7

Clear it when you return to that window (from a tmux hook):

set-hook -g pane-focus-in       'run-shell -b "pager remove --key #{window_id}"'
set-hook -g after-select-window 'run-shell -b "pager remove --key #{window_id}"'

A launchd-started agent does not inherit your interactive PATH, so use an absolute path to pager in the hook if pager is not resolvable there.

Logs

The agent logs the page/click/remove lifecycle to the unified log under the cz.szypowi.pager subsystem. Most of the flow is logged below the persisted level, so watch it live with --level debug:

log stream --level debug --predicate 'subsystem == "cz.szypowi.pager"'

Build from source

make build      # build and sign Pager.app into .build/
make install    # build, then copy to /Applications
make dev        # ad-hoc build and (re)launch the agent for local testing

scripts/generate-icon.swift regenerates the app icon.

License

MIT

About

Click-to-focus, self-clearing macOS notifications, driven from a shell or a tmux hook

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages