notifier

package
v1.48.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: GPL-2.0 Imports: 34 Imported by: 0

Documentation

Overview

ABOUTME: terminal-bell delivery on Unix-likes — write BEL to /dev/tty, with a ABOUTME: tmux pane-tty fallback for hook subprocesses that lack a controlling tty.

ABOUTME: Linux-specific notification handling with click-to-focus support. ABOUTME: Uses background daemon for persistent D-Bus connection when click-to-focus is enabled.

ABOUTME: Fills the zellij half of the focus hints the Linux daemon acts on. ABOUTME: Linux-only because FocusHints is the daemon's IPC payload, and the daemon is Linux-only.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnsureClaudeNotificationsApp

func EnsureClaudeNotificationsApp() error

EnsureClaudeNotificationsApp is a no-op on Linux.

func FocusAppWindow

func FocusAppWindow(bundleID, cwd string) error

FocusAppWindow is not supported on non-darwin platforms.

func FocusAppWindowWithOptions

func FocusAppWindowWithOptions(bundleID, cwd string, opts FocusWindowOptions) error

FocusAppWindowWithOptions is not supported on non-darwin platforms.

func GetKittyWindowTarget

func GetKittyWindowTarget() (windowID, listenOn string, err error)

GetKittyWindowTarget returns the window ID and listen socket path from environment variables.

func GetTerminalBundleID

func GetTerminalBundleID(configOverride string) string

GetTerminalBundleID returns empty string on Linux as terminal bundle IDs are a macOS-specific concept.

func GetTerminalNotifierPath

func GetTerminalNotifierPath() (string, error)

GetTerminalNotifierPath returns an error on Linux as terminal-notifier is macOS-only.

func GetTmuxPaneTarget

func GetTmuxPaneTarget() (string, error)

GetTmuxPaneTarget returns the tmux pane ID (e.g. "%42") of the pane where Claude Code is running, for use with tmux select-pane / select-window commands.

Prefers $TMUX_PANE (set by tmux per-pane at creation, always points to the process's own pane) over "tmux display-message" (which returns the currently active pane and may be wrong if the user switched tabs).

func GetWezTermPaneTarget

func GetWezTermPaneTarget() (paneID, socketPath string, err error)

GetWezTermPaneTarget returns the pane ID and unix socket path from environment variables. No external commands needed — the pane ID is already available in $WEZTERM_PANE.

func GetZellijTabTarget

func GetZellijTabTarget() (tabName, sessionName string, err error)

GetZellijTabTarget returns the active tab name and session name for the current zellij session.

dump-layout is answered by the running zellij server, so it can hang in a way that argument parsing cannot. It runs in the hook process, ahead of the notification the user is waiting for, so it is bounded: a session that stops answering costs the deadline and then reports a failed target, which every caller already treats as "no zellij focus" rather than "no notification".

func HookDesktopContent added in v1.47.1

func HookDesktopContent(status analyzer.Status, content HookPresentation, statusTitle string, sessionLabel bool) notification.Content

HookDesktopContent shares literal hook presentation with trusted integrations. Delivery policy, consent and routing remain the caller's responsibility.

func IsDaemonAvailable

func IsDaemonAvailable() bool

IsDaemonAvailable checks if the notification daemon is available and running. Exported for testing and status checks.

func IsDisplayAsleep added in v1.45.17

func IsDisplayAsleep() bool

IsDisplayAsleep reports whether the desktop's display(s) are currently asleep, so callers can mute a sound nobody is present to hear.

Like IsDoNotDisturb, it is deliberately conservative: it returns true ONLY when it can positively confirm every active display is asleep. Any uncertainty - an unsupported platform, a failed system query, or no displays found - returns false, so the notification's sound is delivered. A wrong "asleep" result would silently swallow a cue the user is waiting for; a wrong "awake" result merely plays one sound nobody heard.

Detection is implemented on macOS (see sleep_darwin.go). Every other platform reports "not asleep".

func IsDoNotDisturb added in v1.45.17

func IsDoNotDisturb() bool

IsDoNotDisturb reports whether the desktop session is currently in a Do Not Disturb / notification-inhibited state.

Like IsTerminalFocused, it is deliberately conservative: it returns true ONLY when it can positively confirm that notifications are inhibited. Any uncertainty - an unsupported platform or desktop, a notification daemon that exposes no DND state, a D-Bus error, a timeout, or an unparseable value - returns false, so the notification and its sound are delivered.

This bias matters: a wrong "in DND" result silently swallows a cue the user is waiting for, whereas a wrong "not in DND" result merely plays one sound the user did not want. Probe failures therefore keep the existing delivery behavior. A confirmed positive can intentionally drop desktop delivery in "suppress" mode.

Detection is implemented on Linux (see dnd_linux.go). Every other platform reports "not in DND"; see docs/DO_NOT_DISTURB.md for the per-platform status.

func IsKitty

func IsKitty() bool

IsKitty returns true if the current process is running inside Kitty with remote control enabled. Checks both $KITTY_WINDOW_ID (always set in Kitty) and $KITTY_LISTEN_ON (only set when remote control is configured).

func IsTerminalFocused

func IsTerminalFocused(sessionID, cwd string) bool

IsTerminalFocused reports whether the terminal window running Claude Code currently has operating-system focus.

It is deliberately conservative: it returns true ONLY when it can positively confirm that the focused terminal belongs to this session. sessionID and cwd let platform implementations match exact tabs/windows when available. On any uncertainty - an OS API error, an unsupported platform or session (e.g. a Wayland compositor with no generic active-window query), or a terminal it cannot identify - it returns false so the caller still delivers the notification.

This bias matters: a wrong "focused" result silently swallows a notification the user is waiting for, whereas a wrong "unfocused" result merely shows one extra banner. The failure mode is therefore always an extra notification, never a missing one.

func IsTerminalNotifierAvailable

func IsTerminalNotifierAvailable() bool

IsTerminalNotifierAvailable returns false on Linux.

func IsTmux

func IsTmux() bool

IsTmux returns true if the current process is running inside a tmux session.

func IsTmuxControlMode

func IsTmuxControlMode() bool

IsTmuxControlMode returns true if any tmux client attached to the current server is running in control mode (-CC), typically used by iTerm2. In control mode, standard tmux select-window doesn't cause iTerm2 to switch tabs; the iTerm2 Python API must be used instead.

Uses list-clients (not display-message) because display-message evaluates #{client_control_mode} for the temporary command-line client we spawn, which is never in control mode. list-clients enumerates all persistent (attached) clients, so we can check if ANY is in control mode.

func IsWezTerm

func IsWezTerm() bool

IsWezTerm returns true if the current process is running inside WezTerm.

func IsZellij

func IsZellij() bool

IsZellij returns true if the current process is running inside a zellij session.

func MaybeCaptureGhosttyTerminalID

func MaybeCaptureGhosttyTerminalID(configOverride, sessionID, cwd string)

func SendQuickNotification

func SendQuickNotification(title, message, executeCmd string) error

SendQuickNotification sends a one-off notification without requiring a Notifier instance. executeCmd is the shell command run when the user clicks the notification (may be empty).

func SetupPermission added in v1.44.0

func SetupPermission(ctx context.Context, root string, expected installruntime.InstalledSnapshot, request bool) (string, error)

SetupPermission is an explicit installer action. It never changes enablement, sends a notification, repairs an installation, or retries authorization.

func StartDaemon

func StartDaemon() bool

StartDaemon starts the notification daemon on-demand. Returns true if daemon started successfully or was already running.

func StopDaemon

func StopDaemon() error

StopDaemon stops the running notification daemon.

func ZellijSupportsPaneFocus added in v1.43.1

func ZellijSupportsPaneFocus() bool

ZellijSupportsPaneFocus reports whether the installed zellij accepts focus-pane-id, which arrived in 0.44.1.

It asks zellij rather than comparing version numbers: `action <name> --help` exits zero for a subcommand that exists and non-zero for one that does not, needs no running session, and leaves the running one alone. Any non-zero status counts as unsupported, which is what carries this across zellij's clap upgrade — the exit code for an unknown subcommand changed from 1 to 2 — and what makes an absent zellij degrade to the target that has always existed rather than to no focus at all.

The probe is bounded: it runs in the hook process, ahead of the notification the user is waiting for, so a zellij that never answers must not hold that up. A timeout reads as unsupported, which lands on the tab path.

Types

type BootClock added in v1.44.0

type BootClock interface {
	Now() (bootID string, seconds float64, err error)
}

BootClock uses the same boot epoch and continuous seconds as native. Callers use Now at transport admission; Deliver never starts a new request budget.

type FocusWindowOptions

type FocusWindowOptions struct {
	GhosttyTerminalID string
}

type FreedesktopDelivery added in v1.44.0

type FreedesktopDelivery struct {
	Clock BootClock
	Open  func(context.Context) (sessionNotifications, error)
}

FreedesktopDelivery is the Linux explicit-notify adapter. It makes one session-bus Notify call and never falls back after a possible D-Bus effect.

func NewFreedesktopDelivery added in v1.44.0

func NewFreedesktopDelivery(clock BootClock) *FreedesktopDelivery

func (*FreedesktopDelivery) CheckReadiness added in v1.44.0

func (*FreedesktopDelivery) Deliver added in v1.44.0

type HookPresentation added in v1.47.1

type HookPresentation struct {
	SessionName string
	Branch      string
	Folder      string
	Body        string
	Question    string
}

HookPresentation carries literal hook content without the legacy bracket envelope. It affects presentation only, never navigation or consent.

type LocalAuthorityLease added in v1.47.1

type LocalAuthorityLease interface {
	NativeLease
	BeforeHandoff(context.Context) error
	Complete(context.Context) error
}

LocalAuthorityLease adds result-bearing checks to the one retained lease. Release-only adapters remain compatible. Complete runs on every acquired Local return, before Release; it must never acquire another installation lease.

type ManagedInstallation added in v1.44.0

type ManagedInstallation struct {
	ControlRoot string
	Expected    installruntime.InstalledSnapshot
}

ManagedInstallation belongs to one request. Expected is the unchanged, immutable Installation from that request's ReadPolicySnapshot. Use the same adapter for readiness and delivery, never a startup-global snapshot. Acquire must run without a journal lock and never refreshes a stale snapshot. The kernel owns root, permanent lock, generation, owner and artifact checks.

func (ManagedInstallation) Acquire added in v1.44.0

type ManagedNativeProcess added in v1.44.0

type ManagedNativeProcess struct{}

ManagedNativeProcess executes only the paths returned by a verified lease. It never uses PATH lookup, bundle-ID lookup, or the legacy notifier finder.

func (ManagedNativeProcess) Launch added in v1.44.0

func (ManagedNativeProcess) Launch(ctx context.Context, bundle, request, receipt string) (bool, error)

func (ManagedNativeProcess) Probe added in v1.44.0

func (ManagedNativeProcess) Probe(ctx context.Context, executable string) ([]byte, error)

func (ManagedNativeProcess) ProbePermission added in v1.44.0

func (ManagedNativeProcess) ProbePermission(ctx context.Context, executable, correlation, nonce string) ([]byte, error)

func (ManagedNativeProcess) ProbeSetup added in v1.44.0

func (ManagedNativeProcess) ProbeSetup(ctx context.Context, executable string) ([]byte, error)

ProbeSetup is used only after the installer has verified the managed artifact and its base capabilities. It never requests OS authorization.

func (ManagedNativeProcess) RequestPermission added in v1.44.0

func (ManagedNativeProcess) RequestPermission(ctx context.Context, executable, correlation, nonce string) ([]byte, error)

RequestPermission is an explicit installer action, never a notify fallback. The caller must hold a setup lease and first validate setup capabilities. A timeout cannot retract an OS prompt already dispatched; never auto-retry.

type NativeAttempt added in v1.44.0

type NativeAttempt struct{ Directory, RequestPath, ReceiptPath, Nonce string }

type NativeInstallation added in v1.44.0

type NativeInstallation interface {
	Acquire(context.Context) (NativeLease, error)
}

NativeInstallation is a lease from the managed component owner. Verify must check its trusted offline manifest/fingerprint before returning a path; unknown helpers must never be executed to discover support. The lease pins the stable bundle against update through handoff. PR2 composition owns that lock/ledger.

type NativeLease added in v1.44.0

type NativeLease interface {
	BundlePath() string
	ExecutablePath() string
	Release()
}

type NativeProcess added in v1.44.0

type NativeProcess interface {
	Probe(context.Context, string) ([]byte, error)
	// Launch returns whether a process might have handed work to LaunchServices.
	// A started launcher is an unknown outcome until a correlated native receipt.
	Launch(context.Context, string, string, string) (mayHaveHandedOff bool, err error)
}

type NativeSpool added in v1.44.0

type NativeSpool interface {
	Prepare(context.Context, notification.Request, func(string) ([]byte, error)) (NativeAttempt, error)
	Receipt(NativeAttempt) ([]byte, error)
	// Expire removes only this owned attempt after the original boot deadline.
	// It must not be called merely because the open launcher exited.
	Expire(NativeAttempt) error
}

type NotificationPermissionDeniedError

type NotificationPermissionDeniedError struct {
	Details string
}

NotificationPermissionDeniedError indicates macOS rejected the native ClaudeNotifier path because notification permission is denied for the app.

func (*NotificationPermissionDeniedError) Error

type Notifier

type Notifier struct {
	// contains filtered or unexported fields
}

Notifier sends desktop notifications

func New

func New(cfg *config.Config) *Notifier

New creates a new notifier

func (*Notifier) Close

func (n *Notifier) Close() error

Close waits for all sounds to finish playing and cleans up resources

func (*Notifier) SendDesktop

func (n *Notifier) SendDesktop(status analyzer.Status, message, sessionID, cwd string, opts ...SendOption) error

SendDesktop sends a desktop notification. On macOS, it always prefers ClaudeNotifier/terminal-notifier to avoid Script Editor attribution and optionally enables click-to-focus. On Linux with clickToFocus enabled, it uses the background daemon. cwd is the working directory of the project; used for window-specific focus. May be empty.

Passing WithoutSound() delivers the banner without the plugin's own audio cue (used when the desktop is in Do Not Disturb).

type PermissionProcess added in v1.44.0

type PermissionProcess interface {
	ProbePermission(context.Context, string, string, string) ([]byte, error)
}

PermissionProcess extends only the known capability mode; unsupported implementations fail closed. Acquire must verify offline before any execution.

type PrivateNativeSpool added in v1.44.0

type PrivateNativeSpool struct {
	Root  string
	Clock BootClock
}

PrivateNativeSpool is used only by notify/setup, never read-only status. Root must already be a setup-owned private directory on a local filesystem. All physical paths are derived from EvalSymlinks before creating files.

func (*PrivateNativeSpool) Cleanup added in v1.44.0

func (s *PrivateNativeSpool) Cleanup(ctx context.Context) error

Cleanup is an explicit bounded notify/setup operation. It is not a status API.

func (*PrivateNativeSpool) Expire added in v1.44.0

func (s *PrivateNativeSpool) Expire(a NativeAttempt) error

func (*PrivateNativeSpool) Prepare added in v1.44.0

func (s *PrivateNativeSpool) Prepare(ctx context.Context, r notification.Request, encode func(string) ([]byte, error)) (NativeAttempt, error)

func (*PrivateNativeSpool) Receipt added in v1.44.0

func (s *PrivateNativeSpool) Receipt(a NativeAttempt) ([]byte, error)

type SendOption added in v1.45.17

type SendOption func(*sendOptions)

SendOption customises a single desktop notification delivery.

func WithHookPresentation added in v1.47.1

func WithHookPresentation(content HookPresentation) SendOption

func WithoutSound added in v1.45.17

func WithoutSound() SendOption

WithoutSound delivers the notification without playing the plugin's own audio cue. The banner is still sent, so it reaches the desktop's notification centre and is visible once the user is available again.

type StructuredDelivery added in v1.44.0

type StructuredDelivery struct {
	Installation NativeInstallation
	Clock        BootClock
	Process      NativeProcess
	Spool        NativeSpool
}

StructuredDelivery is independent of Notifier's legacy best-effort policy. Dependencies are immutable and safe for concurrent requests. No globals, hook state, analyzer, bell, webhook or fallback are consulted by Deliver.

func NewStructuredDelivery added in v1.44.0

func NewStructuredDelivery(installation NativeInstallation, spoolRoot string) *StructuredDelivery

NewStructuredDelivery wires the real managed launcher, private spool and boot-continuous clock. Construction is read-only: setup must already have prepared spoolRoot and the trusted installation. PR4 owns policy/origin and durable admission before calling this port.

func (*StructuredDelivery) CheckReadiness added in v1.44.0

CheckReadiness releases its lease before returning; callers may then enter their journal transaction. Ready is a snapshot, never a delivery guarantee.

func (*StructuredDelivery) Deliver added in v1.44.0

type SystemBootClock added in v1.44.0

type SystemBootClock struct{}

SystemBootClock uses the same trusted procfs boot identity and CLOCK_BOOTTIME epoch as the production journal clock, with nanosecond precision for deadlines.

func (SystemBootClock) Now added in v1.44.0

func (SystemBootClock) Now() (string, float64, error)

type WindowsToastDelivery added in v1.44.0

type WindowsToastDelivery struct {
	Clock BootClock
	Open  func(context.Context) (windowsToastSession, error)
}

WindowsToastDelivery is the Windows explicit-notify adapter. It shows one informational toast and never starts the click-to-focus handler or falls back to beeep after a possible toast effect.

func NewWindowsToastDelivery added in v1.44.0

func NewWindowsToastDelivery(clock BootClock) *WindowsToastDelivery

func (*WindowsToastDelivery) CheckReadiness added in v1.44.0

func (*WindowsToastDelivery) Deliver added in v1.44.0

Directories

Path Synopsis
Package nativeprotocol validates the versioned native helper replies.
Package nativeprotocol validates the versioned native helper replies.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL