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 Self::with_port(DASHBOARD_PORT)
47 }
48
49 pub fn with_port(port: u16) -> Self {
51 let base_url = format!("http://127.0.0.1:{}", port);
52 let client = reqwest::Client::builder()
53 .timeout(Duration::from_millis(100)) .build()
55 .unwrap_or_else(|_| reqwest::Client::new());
56
57 Self { base_url, client }
58 }
59
60 pub async fn notify(&self, message: NotificationMessage) {
62 if std::env::var("IE_DISABLE_DASHBOARD_NOTIFICATIONS")
64 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
65 .unwrap_or(false)
66 {
67 tracing::debug!(
68 "Dashboard notifications disabled via IE_DISABLE_DASHBOARD_NOTIFICATIONS"
69 );
70 return; }
72
73 let url = format!("{}/api/internal/cli-notify", self.base_url);
74
75 if let Err(e) = self.client.post(&url).json(&message).send().await {
77 tracing::debug!("Failed to notify Dashboard: {}", e);
78 }
80 }
81
82 pub async fn notify_task_changed(
84 &self,
85 task_id: Option<i64>,
86 operation: &str,
87 project_path: Option<String>,
88 ) {
89 self.notify(NotificationMessage::TaskChanged {
90 task_id,
91 operation: operation.to_string(),
92 project_path,
93 })
94 .await;
95 }
96
97 pub async fn notify_event_added(
99 &self,
100 task_id: i64,
101 event_id: i64,
102 project_path: Option<String>,
103 ) {
104 self.notify(NotificationMessage::EventAdded {
105 task_id,
106 event_id,
107 project_path,
108 })
109 .await;
110 }
111
112 pub async fn notify_workspace_changed(
114 &self,
115 current_task_id: Option<i64>,
116 project_path: Option<String>,
117 ) {
118 self.notify(NotificationMessage::WorkspaceChanged {
119 current_task_id,
120 project_path,
121 })
122 .await;
123 }
124}
125
126impl Default for CliNotifier {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_notifier_creation() {
138 let notifier = CliNotifier::new();
139 assert_eq!(notifier.base_url, "http://127.0.0.1:11391");
140 }
141
142 #[test]
143 fn test_notifier_with_custom_port() {
144 let notifier = CliNotifier::with_port(8080);
145 assert_eq!(notifier.base_url, "http://127.0.0.1:8080");
146 }
147
148 #[tokio::test]
149 async fn test_notify_non_blocking() {
150 let notifier = CliNotifier::with_port(65000); notifier
153 .notify_task_changed(Some(42), "created", Some("/test/path".to_string()))
154 .await;
155
156 }
159}