// internal/notify/notify.go // Package notify is Keel's desktop-notification effector: a best-effort // "interrupt the user" primitive behind a Notifier port. On Linux with // notify-send installed it pops a toast; everywhere else it is a silent nop. // Mirrors internal/enforce: a pure OS primitive, all policy lives in callers. package notify import ( "context" "fmt" "os/exec" ) // Notifier shows a desktop notification. Best-effort: it returns an error for // diagnostics, but callers never block on it and treat failure as "no toast." type Notifier interface { Notify(ctx context.Context, title, body string) error } // NewNotifier returns the notify-send adapter when that binary is on PATH, else // a nop. This makes a missing binary (or a non-Linux host) degrade to silence // rather than an error. func NewNotifier() Notifier { if path, err := exec.LookPath("notify-send"); err == nil { return sendNotifier{bin: path} } return nopNotifier{} } // sendNotifier shells out to notify-send. The app name groups Keel's toasts. type sendNotifier struct{ bin string } func (s sendNotifier) Notify(ctx context.Context, title, body string) error { cmd := exec.CommandContext(ctx, s.bin, "--app-name=Keel", title, body) if err := cmd.Run(); err != nil { return fmt.Errorf("notify: notify-send: %w", err) } return nil } // nopNotifier is the silent fallback. type nopNotifier struct{} func (nopNotifier) Notify(context.Context, string, string) error { return nil }