intent_engine/dashboard/
cli_notifier.rs1use std::time::Duration;
7
8const DASHBOARD_PORT: u16 = 11391;
10
11#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum NotificationMessage {
15 TaskChanged {
17 task_id: Option<i64>,
18 operation: String,
19 project_path: Option<String>,
21 },
22 EventAdded {
24 task_id: i64,
25 event_id: i64,
26 project_path: Option<String>,
28 },
29 WorkspaceChanged {
31 current_task_id: Option<i64>,
32 project_path: Option<String>,
34 },
35}
36
37pub struct CliNotifier {
39 base_url: String,
40 client: reqwest::Client,
41}
42
43impl CliNotifier {
44 pub fn new() -> Self {
46 let base_url = std::env::var("IE_DASHBOARD_BASE_URL")
47 .unwrap_or_else(|_| format!("http://127.0.0.1:{}", DASHBOARD_PORT));
48 Self::with_base_url(base_url)
49 }
50
51 pub fn with_base_url(base_url: String) -> Self {
53 let client = reqwest::Client::builder()
54 .timeout(Duration::from_millis(100)) .build()
56 .unwrap_or_else(|_| reqwest::Client::new());
57
58 Self { base_url, client }
59 }
60
61 pub fn with_port(port: u16) -> Self {
63 let base_url = format!("http://127.0.0.1:{}", port);
64 Self::with_base_url(base_url)
65 }
66
67 pub async fn notify(&self, message: NotificationMessage) {
69 if std::env::var("IE_DISABLE_DASHBOARD_NOTIFICATIONS")
71 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
72 .unwrap_or(false)
73 {
74 tracing::debug!(
75 "Dashboard notifications disabled via IE_DISABLE_DASHBOARD_NOTIFICATIONS"
76 );
77 return; }
79
80 let url = format!("{}/api/internal/cli-notify", self.base_url);
81
82 if let Err(e) = self.client.post(&url).json(&message).send().await {
84 tracing::debug!("Failed to notify Dashboard: {}", e);
85 }
87 }
88
89 pub async fn notify_task_changed(
91 &self,
92 task_id: Option<i64>,
93 operation: &str,
94 project_path: Option<String>,
95 ) {
96 self.notify(NotificationMessage::TaskChanged {
97 task_id,
98 operation: operation.to_string(),
99 project_path,
100 })
101 .await;
102 }
103
104 pub async fn notify_event_added(
106 &self,
107 task_id: i64,
108 event_id: i64,
109 project_path: Option<String>,
110 ) {
111 self.notify(NotificationMessage::EventAdded {
112 task_id,
113 event_id,
114 project_path,
115 })
116 .await;
117 }
118
119 pub async fn notify_workspace_changed(
121 &self,
122 current_task_id: Option<i64>,
123 project_path: Option<String>,
124 ) {
125 self.notify(NotificationMessage::WorkspaceChanged {
126 current_task_id,
127 project_path,
128 })
129 .await;
130 }
131}
132
133impl Default for CliNotifier {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_notifier_creation() {
145 let notifier = CliNotifier::new();
146 assert_eq!(notifier.base_url, "http://127.0.0.1:11391");
147 }
148
149 #[test]
150 fn test_notifier_with_custom_port() {
151 let notifier = CliNotifier::with_port(8080);
152 assert_eq!(notifier.base_url, "http://127.0.0.1:8080");
153 }
154
155 #[tokio::test]
156 async fn test_notify_non_blocking() {
157 let notifier = CliNotifier::with_port(65000); notifier
160 .notify_task_changed(Some(42), "created", Some("/test/path".to_string()))
161 .await;
162
163 }
166}