Skip to content

mshakeg/notify-hook

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 

Repository files navigation

notify-hook

Shared bash library for macOS desktop-notification hooks. Used by parallel Claude Code and Codex CLI dotfiles (mshakeg/claude-dotfiles, mshakeg/codex-dotfiles) so a single canonical implementation drives both, with click-to-focus integration into the mshakeg/vscode-terminal-focus VS Code extension.

What it does

Each app's hook framework (Claude Code or Codex CLI) calls a per-app hooks/notify.sh script on events like Stop, PermissionRequest, etc. That script is now a thin wrapper that:

  1. Sets per-app config (title, icon, env-var prefix, sound + timeout per notification type, transparent-process list).
  2. Sources lib.sh from this repo.
  3. Calls notify_main "$@".

lib.sh then handles the shared logic:

  • Auto-closing macOS notifications via alerter, sent as com.apple.ScriptEditor2 so banners still appear when VS Code is frontmost.
  • Throttling fast Stop turns (configurable via ${PREFIX}_MIN_ELAPSED).
  • Walking the parent process chain to find the integrated terminal's outermost shell PID.
  • Looking that PID up in the terminal-focus extension's window registry.
  • On click: code <workspace> to bring the right window to the foreground, then a windowKey-gated vscode://mshakeg.terminal-focus/focus?pid=…&w=… URI so the right terminal in that window focuses.
  • Graceful fallback to code <cwd> if the extension isn't installed or the PID isn't registered.
  • Opt-in debug logging via ${PREFIX}_DEBUG=1 (path: ${PREFIX}_DEBUG_LOG, default /tmp/notify-hook.log).

Install

git clone https://github.com/mshakeg/notify-hook.git ~/dev/dotfiles/notify-hook
ln -s ~/dev/dotfiles/notify-hook ~/.notify-hook

Wrapper scripts source ~/.notify-hook/lib.sh. If you clone the repo elsewhere, adjust the symlink (or set NOTIFY_HOOK_LIB=/path/to/lib.sh in your wrapper).

Dependencies (macOS):

brew install vjeantet/tap/alerter jq

If either is missing, the hook exits silently.

Wrapper API

A minimal wrapper looks like:

#!/usr/bin/env bash
set -eu

NOTIFY_APP_TITLE="Claude Code"
NOTIFY_STATE_DIR_NAME="claude-notify"
NOTIFY_ENV_PREFIX="CLAUDE_NOTIFY"
NOTIFY_ICON="$(cd "$(dirname "$0")" && pwd -P)/../icons/claude.png"
NOTIFY_TRANSPARENT_PROCS=(claude)

# Per-type sound + auto-close timeout. macOS ships with bash 3.2, which
# has no associative arrays — define these as functions instead.
notify_sound() {
  case "$1" in
    perm)   echo Funk   ;;
    stop)   echo Bottle ;;
    elicit) echo Ping   ;;
    web)    echo Pop    ;;
    idle)   echo        ;;  # silent
    *)      echo default ;;
  esac
}
notify_timeout() {
  case "$1" in
    perm|stop|elicit|web) echo 120 ;;
    idle)                 echo 0   ;;  # persistent
    *)                    echo 120 ;;
  esac
}

source "${NOTIFY_HOOK_LIB:-$HOME/.notify-hook/lib.sh}"
notify_main "$@"

Required vars

Var Purpose
NOTIFY_APP_TITLE Popup title; e.g. "Claude Code" or "Codex".
NOTIFY_STATE_DIR_NAME Per-session timestamp directory name (under $TMPDIR).
NOTIFY_ENV_PREFIX Prefix for runtime env vars: ${PREFIX}_MIN_ELAPSED, ${PREFIX}_DEBUG, ${PREFIX}_DEBUG_LOG.

Required functions

Function Contract
notify_sound <type> Echo the /System/Library/Sounds basename for <type>, or empty for silent.
notify_timeout <type> Echo the auto-close seconds for <type> (0 = persistent until dismissed).

Optional vars

Var Default Purpose
NOTIFY_ICON (none) Path to a PNG to show in the popup.
NOTIFY_TRANSPARENT_PROCS empty Array of process basenames find_shell_pid should walk through (e.g. (claude) or (codex)).
NOTIFY_GROUP_PREFIX lowercased app title alerter --group prefix for notification replacement.
NOTIFY_EXTENSION_ID mshakeg.terminal-focus vscode://<ID>/focus URI authority.
NOTIFY_REGISTRY_PATH ~/Library/Application Support/code-terminal-focus/windows.json Where to read the per-window PID registry from.

Two modes

The wrapper invokes notify_main "$@" for both:

  • notify.sh prompt-start — wire to the UserPromptSubmit hook. Records a session timestamp; produces no popup.
  • notify.sh <type> <message> — wire to Stop, PermissionRequest, etc. Posts a notification with sound/timeout looked up via <type>.

Type strings are arbitrary — lib.sh doesn't enforce a fixed set. Whatever cases the wrapper's notify_sound() / notify_timeout() functions handle defines the supported types (anything else falls through to the *) default).

Debugging

export CLAUDE_NOTIFY_DEBUG=1
# … fire a notification …
cat /tmp/notify-hook.log

Logs the resolved click_cmd, find_shell_pid result, code invocation, and alerter outcome (@CONTENTCLICKED vs @TIMEOUT). Override the path via ${PREFIX}_DEBUG_LOG=/somewhere/else.

Why a separate repo?

The two dotfiles repos (claude-dotfiles, codex-dotfiles) are versioned independently — they couldn't share files via a single repo without one consuming the other. Pulling the shared notification logic out into notify-hook lets both depend on the same upstream and means a bugfix or feature lands once, not twice.

About

Shared bash library for macOS desktop-notification hooks. Drives mshakeg/claude-dotfiles and mshakeg/codex-dotfiles with a single canonical implementation, including click-to-focus via mshakeg/vscode-terminal-focus.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages