Skip to main content

zeph_config/
notifications.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Configuration for the per-turn completion notification subsystem.
5//!
6//! Notifications are best-effort, fire-and-forget signals sent after each agent turn
7//! completes. Two channels are supported: macOS native banners (via `osascript`)
8//! and an ntfy-compatible JSON webhook POST.
9//!
10//! # Defaults
11//!
12//! All fields default to disabled so existing configs are not affected.
13//!
14//! # Examples
15//!
16//! ```toml
17//! [notifications]
18//! enabled = true
19//! macos_native = true
20//! webhook_url = "https://ntfy.sh"
21//! webhook_topic = "my-topic-here"
22//! title = "Zeph"
23//! min_turn_duration_ms = 3000
24//! only_on_error = false
25//! ```
26
27use serde::{Deserialize, Serialize};
28
29fn default_title() -> String {
30    "Zeph".to_owned()
31}
32
33/// Configuration for the per-turn completion notifier.
34///
35/// Both channels (macOS and webhook) are independently enableable.
36/// At least one channel must be reachable for a notification to fire.
37// Config structs legitimately use multiple boolean flags — each maps to a distinct TOML key.
38#[allow(clippy::struct_excessive_bools)] // config struct — boolean flags are idiomatic for TOML-deserialized configuration
39#[derive(Debug, Clone, Deserialize, Serialize)]
40pub struct NotificationsConfig {
41    /// Master switch. When `false`, no notifications are sent regardless of other fields.
42    #[serde(default)]
43    pub enabled: bool,
44
45    /// Send a macOS Notification Center banner via `osascript`.
46    ///
47    /// Silently no-ops on non-macOS platforms.
48    #[serde(default)]
49    pub macos_native: bool,
50
51    /// URL for the ntfy-compatible webhook endpoint (e.g. `"https://ntfy.sh"`).
52    ///
53    /// Empty string or absent means the webhook channel is disabled.
54    #[serde(default)]
55    pub webhook_url: Option<String>,
56
57    /// ntfy topic. Required when `webhook_url` is set; ignored otherwise.
58    #[serde(default)]
59    pub webhook_topic: Option<String>,
60
61    /// Notification title shown in banners and webhook payloads.
62    #[serde(default = "default_title")]
63    pub title: String,
64
65    /// Minimum successful-turn wall-clock duration in milliseconds before a notification fires.
66    ///
67    /// Set to `0` to always notify. Does NOT apply to error turns — errors always fire
68    /// regardless of duration.
69    #[serde(default)]
70    pub min_turn_duration_ms: u64,
71
72    /// When `true`, only fire on turns that completed with an error.
73    #[serde(default)]
74    pub only_on_error: bool,
75
76    /// Allow non-HTTPS webhook URLs.
77    ///
78    /// When `false` (the default) only `https://` webhook URLs are accepted.
79    /// Set to `true` to allow `http://` URLs for local testing only — never use
80    /// in production as the notification payload is sent in plaintext.
81    #[serde(default)]
82    pub webhook_allow_insecure: bool,
83}
84
85impl Default for NotificationsConfig {
86    fn default() -> Self {
87        Self {
88            enabled: false,
89            macos_native: false,
90            webhook_url: None,
91            webhook_topic: None,
92            title: default_title(),
93            min_turn_duration_ms: 0,
94            only_on_error: false,
95            webhook_allow_insecure: false,
96        }
97    }
98}