notify

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package notify emits dep-free terminal notifications (BEL and/or OSC-9 desktop notifications) when Zero finishes a turn or needs user input.

Index

Constants

View Source
const (
	// EnvWebhookURL holds the destination webhook/Slack URL. Empty disables it.
	EnvWebhookURL = "ZERO_NOTIFY_WEBHOOK_URL"
	// EnvWebhookSummary is an optional one-line run summary attached to every
	// payload (for example "nightly audit run").
	EnvWebhookSummary = "ZERO_NOTIFY_WEBHOOK_SUMMARY"
)

Webhook delivery is configured entirely from the environment. A webhook URL typically embeds a secret token, so sourcing it from the environment keeps it out of any on-disk config file. The sink is strictly opt-in: with EnvWebhookURL unset the wiring helper attaches nothing and the notifier behaves exactly as before.

Variables

This section is empty.

Functions

func DefaultMessage

func DefaultMessage(event Event) string

DefaultMessage is the generic OSC-9 body for an event (no prompt content).

func Enabled

func Enabled(mode Mode) bool

Enabled reports whether mode will ever emit a notification.

func MaybeAddWebhookSink

func MaybeAddWebhookSink(n *Notifier, env func(string) string, logf func(format string, args ...any))

MaybeAddWebhookSink attaches a webhook sink to n when a webhook URL is present in the environment, and is otherwise a no-op. env resolves an environment variable (pass os.Getenv); logf records one redacted line per failed delivery (pass nil to stay silent — for example a TUI that owns the screen). It is safe to call unconditionally: configuration alone decides whether the sink exists.

The attached sink is still subject to the notifier's Mode/focus policy, so a webhook only delivers when notifications are enabled (for example `--notify both`), matching the rest of the notification surface.

Types

type Config

type Config struct {
	Mode      Mode
	FocusMode FocusMode
}

Config is the resolved notifier policy. Zero value (Mode=="") is silent.

type Event

type Event int

Event is the moment that triggered a notification.

const (
	Completion Event = iota
	AwaitingInput
)

type FocusMode

type FocusMode string

FocusMode gates emission on terminal focus (a TUI concept).

const (
	FocusUnfocused FocusMode = "unfocused" // default: only when terminal is NOT focused
	FocusAlways    FocusMode = "always"
	FocusFocused   FocusMode = "focused"
)

type Mode

type Mode string

Mode selects the notification mechanism.

const (
	ModeOff    Mode = "off"
	ModeBell   Mode = "bell"
	ModeNotify Mode = "notify" // OSC-9 desktop notification
	ModeBoth   Mode = "both"
)

type Notifier

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

Notifier emits notifications to w according to cfg, and fans them out to any attached Sinks. Safe for concurrent use.

func New

func New(w io.Writer, cfg Config) *Notifier

New returns a Notifier. focused defaults to false so a headless caller (no focus signal) still emits under the default "unfocused" focus mode; an interactive caller should call SetFocused(true) at launch.

func (*Notifier) AddSink

func (n *Notifier) AddSink(sink Sink)

AddSink registers an additional destination that receives every eligible event (subject to the same mode/focus policy as the terminal). Sinks fire even when the Notifier has no terminal writer, so a headless CI run can still reach Slack. Safe to call concurrently with Notify.

func (*Notifier) Notify

func (n *Notifier) Notify(event Event, message string)

Notify emits a notification for event if policy allows. message is the OSC-9 body (ignored for bell) and is also forwarded verbatim to every sink. Write errors are intentionally ignored — a failed notification must never disrupt the run.

The terminal sequence is gated by Mode (bell vs OSC-9) and by the focus policy; sinks are gated only by "notifications enabled" plus the focus policy, because a sink is a separate channel and not bound to the terminal mechanism. Sinks are invoked outside the lock so a slow/blocking sink cannot stall a concurrent Notify or SetFocused.

func (*Notifier) SetFocused

func (n *Notifier) SetFocused(focused bool)

SetFocused records the terminal focus state (TUI FocusMsg/BlurMsg).

type Sink

type Sink interface {
	Emit(event Event, message string)
}

Sink is an additional notification destination beyond the terminal (bell / OSC-9). A webhook/Slack sink is the canonical implementation: it lets an unattended run report "finished / needs input / verify failed" to a chat channel. Emit is fire-and-forget — it must never return an error, panic out, or block the run for long, so an unreachable endpoint cannot disrupt the agent. Implementations are responsible for their own redaction and timeouts.

type WebhookConfig

type WebhookConfig struct {
	// URL is the destination. Empty disables the sink.
	URL string
	// Summary is an optional run summary attached to every emitted payload.
	Summary string
	// Links are optional labeled URLs attached to every emitted payload.
	Links []WebhookLink
	// Client is the HTTP client used for delivery. When nil a client with a
	// conservative timeout is used; its default transport honors HTTP(S)_PROXY
	// when a proxy is configured.
	Client *http.Client
	// Logf records a single line per failed delivery. Lines are passed through
	// the repo redaction before being written, so a token in the URL or message
	// is never logged in the clear. When nil, failures are silent.
	Logf func(format string, args ...any)
	// ExtraSecrets are literal values (for example the resolved API key) that must
	// be masked in the payload and logs in addition to the built-in patterns.
	ExtraSecrets []string
}

WebhookConfig configures a WebhookSink. URL is a Slack incoming-webhook URL or any generic endpoint that accepts a JSON POST. The zero value (empty URL) yields an inert sink whose Emit is a no-op, so callers can wire a sink unconditionally and let configuration decide whether it fires.

type WebhookLink struct {
	Label string `json:"label,omitempty"`
	URL   string `json:"url"`
}

WebhookLink is an optional labeled URL attached to a notification (for example a link to the CI run, the opened PR, or the session log).

type WebhookSink

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

WebhookSink delivers notifications to a webhook/Slack endpoint. It implements Sink. Delivery is best-effort and fails soft: a non-2xx response or a transport error is logged (redacted) and swallowed so it can never disrupt the run.

func NewWebhookSink

func NewWebhookSink(cfg WebhookConfig) *WebhookSink

NewWebhookSink builds a WebhookSink from cfg. A blank URL produces an inert sink (Emit is a no-op).

func (*WebhookSink) Emit

func (s *WebhookSink) Emit(event Event, message string)

Emit builds the JSON payload for event/message and POSTs it to the webhook. It is fire-and-forget: every failure path logs (redacted) and returns nil-ish rather than propagating, satisfying the Sink contract that a notification must never crash the run.

Jump to

Keyboard shortcuts

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