1#![allow(dead_code)]
9
10use super::{Capability, IntegrationResult};
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Hook {
18 pub id: String,
20 pub name: String,
22 pub description: Option<String>,
24 pub enabled: bool,
26 pub trigger: HookTrigger,
28 pub conditions: Vec<HookCondition>,
30 pub actions: Vec<HookAction>,
32 pub config: HookConfig,
34 pub created_at: DateTime<Utc>,
36 pub last_run: Option<DateTime<Utc>>,
38 pub run_count: u64,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(tag = "type", rename_all = "snake_case")]
45pub enum HookTrigger {
46 Schedule {
51 cron: String,
52 timezone: Option<String>,
53 },
54 Interval { seconds: u64 },
56 DateTime { at: DateTime<Utc> },
58 Daily { times: Vec<String> }, Webhook {
66 path: String,
67 method: Option<String>,
68 },
69 FileChange {
71 path: String,
72 events: Vec<FileEvent>,
73 recursive: bool,
74 },
75 EmailReceived {
77 account: String,
78 filters: Option<EmailFilters>,
79 },
80 CalendarEvent {
82 calendar_id: String,
83 event_type: CalendarEventType,
84 minutes_before: Option<i32>,
85 },
86 MessageReceived {
88 platform: String,
89 channel: Option<String>,
90 from: Option<String>,
91 contains: Option<String>,
92 },
93 GitEvent {
95 repo: String,
96 events: Vec<GitEventType>,
97 },
98 SystemEvent { event: SystemEventType },
100 DeviceEvent {
102 device_id: String,
103 state_change: Option<String>,
104 },
105 Location {
107 latitude: f64,
108 longitude: f64,
109 radius_meters: u32,
110 on_enter: bool,
111 on_exit: bool,
112 },
113 Manual,
115 Chain { hook_id: String },
117 AgentRequest { agent_name: Option<String> },
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123#[serde(rename_all = "snake_case")]
124pub enum FileEvent {
125 Created,
126 Modified,
127 Deleted,
128 Renamed,
129 Any,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct EmailFilters {
135 pub from: Option<String>,
136 pub subject_contains: Option<String>,
137 pub has_attachment: Option<bool>,
138 pub labels: Option<Vec<String>>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum CalendarEventType {
145 Created,
146 Updated,
147 Deleted,
148 Starting,
149 Ended,
150 Reminder,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(rename_all = "snake_case")]
156pub enum GitEventType {
157 Push,
158 Pull,
159 Commit,
160 Branch,
161 Tag,
162 PullRequest,
163 Issue,
164 Release,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "snake_case")]
170pub enum SystemEventType {
171 Startup,
172 Shutdown,
173 Sleep,
174 Wake,
175 NetworkChange,
176 BatteryLow,
177 StorageLow,
178 AppLaunched { app: String },
179 AppClosed { app: String },
180 ClipboardChange,
181 ScreenLock,
182 ScreenUnlock,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(tag = "type", rename_all = "snake_case")]
188pub enum HookCondition {
189 TimeWindow {
191 start: String,
192 end: String,
193 days: Option<Vec<String>>,
194 },
195 Expression { expr: String },
197 PreviousResult { action_index: usize, success: bool },
199 EnvVar {
201 name: String,
202 value: Option<String>,
203 exists: Option<bool>,
204 },
205 SystemState {
207 state: String,
208 value: serde_json::Value,
209 },
210 RateLimit { max_runs: u32, period_seconds: u64 },
212 Cooldown { seconds: u64 },
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218#[serde(tag = "type", rename_all = "snake_case")]
219pub enum HookAction {
220 CreateCalendarEvent {
225 calendar_id: String,
226 title: String,
227 start: String,
228 end: String,
229 description: Option<String>,
230 attendees: Option<Vec<String>>,
231 location: Option<String>,
232 },
233 SendEmail {
235 account: String,
236 to: Vec<String>,
237 subject: String,
238 body: String,
239 attachments: Option<Vec<String>>,
240 },
241 CreateNote {
243 app: String, title: String,
245 content: String,
246 folder: Option<String>,
247 tags: Option<Vec<String>>,
248 },
249 CreateTask {
251 app: String, title: String,
253 description: Option<String>,
254 due_date: Option<String>,
255 priority: Option<u8>,
256 project: Option<String>,
257 },
258
259 SlackMessage {
264 channel: String,
265 text: String,
266 thread_ts: Option<String>,
267 },
268 DiscordMessage { channel_id: String, content: String },
270 TeamsMessage { channel: String, content: String },
272 TelegramMessage { chat_id: String, text: String },
274 SendSms { to: String, message: String },
276 Notification {
278 title: String,
279 body: String,
280 sound: Option<String>,
281 actions: Option<Vec<String>>,
282 },
283
284 OpenUrl {
289 url: String,
290 browser: Option<String>,
291 },
292 BrowserAutomation { script: String, headless: bool },
294 ScrapeWebpage {
296 url: String,
297 selectors: HashMap<String, String>,
298 },
299 FillForm {
301 url: String,
302 fields: HashMap<String, String>,
303 submit: bool,
304 },
305
306 RunCommand {
311 command: String,
312 args: Vec<String>,
313 cwd: Option<String>,
314 env: Option<HashMap<String, String>>,
315 },
316 GitOperation {
318 repo: String,
319 operation: String, args: HashMap<String, String>,
321 },
322 CreateGitHubIssue {
324 repo: String,
325 title: String,
326 body: String,
327 labels: Option<Vec<String>>,
328 },
329 DockerCommand {
331 command: String,
332 container: Option<String>,
333 args: Vec<String>,
334 },
335
336 ControlDevice {
341 device_id: String,
342 action: String,
343 parameters: Option<HashMap<String, serde_json::Value>>,
344 },
345 SetScene { scene_id: String },
347 HomeAssistantService {
349 domain: String,
350 service: String,
351 data: Option<serde_json::Value>,
352 },
353
354 CopyFile { source: String, destination: String },
359 MoveFile { source: String, destination: String },
361 CreateFile { path: String, content: String },
363 DeleteFile { path: String },
365 SyncFolder {
367 source: String,
368 destination: String,
369 delete_extra: bool,
370 },
371
372 HttpRequest {
377 method: String,
378 url: String,
379 headers: Option<HashMap<String, String>>,
380 body: Option<String>,
381 },
382 StoreData {
384 key: String,
385 value: serde_json::Value,
386 ttl_seconds: Option<u64>,
387 },
388 QueryDatabase {
390 connection: String,
391 query: String,
392 params: Option<Vec<serde_json::Value>>,
393 },
394
395 RunAgent {
400 agent_name: String,
401 input: String,
402 context: Option<HashMap<String, serde_json::Value>>,
403 },
404 Summarize {
406 content: String,
407 max_length: Option<u32>,
408 },
409 Classify {
411 content: String,
412 categories: Vec<String>,
413 },
414 ExtractData {
416 content: String,
417 schema: serde_json::Value,
418 },
419
420 ChainHook {
425 hook_id: String,
426 data: Option<serde_json::Value>,
427 },
428 Conditional {
430 condition: String,
431 if_true: Box<HookAction>,
432 if_false: Option<Box<HookAction>>,
433 },
434 Parallel { actions: Vec<HookAction> },
436 Delay { seconds: u64 },
438 Log { level: String, message: String },
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct HookConfig {
445 pub max_retries: u32,
447 pub retry_delay_seconds: u64,
449 pub timeout_seconds: u64,
451 pub continue_on_error: bool,
453 pub parallel: bool,
455 pub tags: Vec<String>,
457 pub priority: u8,
459}
460
461impl Default for HookConfig {
462 fn default() -> Self {
463 Self {
464 max_retries: 3,
465 retry_delay_seconds: 5,
466 timeout_seconds: 300,
467 continue_on_error: false,
468 parallel: false,
469 tags: vec![],
470 priority: 50,
471 }
472 }
473}
474
475#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct HookResult {
478 pub hook_id: String,
479 pub success: bool,
480 pub started_at: DateTime<Utc>,
481 pub completed_at: DateTime<Utc>,
482 pub action_results: Vec<ActionResult>,
483 pub error: Option<String>,
484}
485
486#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct ActionResult {
489 pub action_index: usize,
490 pub action_type: String,
491 pub success: bool,
492 pub output: Option<serde_json::Value>,
493 pub error: Option<String>,
494 pub duration_ms: u64,
495}
496
497pub struct HookBuilder {
499 hook: Hook,
500}
501
502impl HookBuilder {
503 pub fn new(name: impl Into<String>) -> Self {
504 Self {
505 hook: Hook {
506 id: uuid::Uuid::new_v4().to_string(),
507 name: name.into(),
508 description: None,
509 enabled: true,
510 trigger: HookTrigger::Manual,
511 conditions: vec![],
512 actions: vec![],
513 config: HookConfig::default(),
514 created_at: Utc::now(),
515 last_run: None,
516 run_count: 0,
517 },
518 }
519 }
520
521 pub fn description(mut self, desc: impl Into<String>) -> Self {
522 self.hook.description = Some(desc.into());
523 self
524 }
525
526 pub fn trigger(mut self, trigger: HookTrigger) -> Self {
527 self.hook.trigger = trigger;
528 self
529 }
530
531 pub fn condition(mut self, condition: HookCondition) -> Self {
532 self.hook.conditions.push(condition);
533 self
534 }
535
536 pub fn action(mut self, action: HookAction) -> Self {
537 self.hook.actions.push(action);
538 self
539 }
540
541 pub fn actions(mut self, actions: Vec<HookAction>) -> Self {
542 self.hook.actions = actions;
543 self
544 }
545
546 pub fn config(mut self, config: HookConfig) -> Self {
547 self.hook.config = config;
548 self
549 }
550
551 pub fn tags(mut self, tags: Vec<String>) -> Self {
552 self.hook.config.tags = tags;
553 self
554 }
555
556 pub fn priority(mut self, priority: u8) -> Self {
557 self.hook.config.priority = priority;
558 self
559 }
560
561 pub fn build(self) -> Hook {
562 self.hook
563 }
564}
565
566pub mod presets {
571 use super::*;
572
573 pub fn morning_briefing() -> Hook {
575 HookBuilder::new("Morning Briefing")
576 .description("Daily morning summary of calendar, emails, and tasks")
577 .trigger(HookTrigger::Daily {
578 times: vec!["07:30".to_string()],
579 })
580 .action(HookAction::RunAgent {
581 agent_name: "assistant".to_string(),
582 input:
583 "Give me a morning briefing: today's calendar, important emails, and top tasks"
584 .to_string(),
585 context: None,
586 })
587 .action(HookAction::Notification {
588 title: "Good Morning!".to_string(),
589 body: "Your daily briefing is ready".to_string(),
590 sound: Some("default".to_string()),
591 actions: None,
592 })
593 .tags(vec!["daily".to_string(), "productivity".to_string()])
594 .build()
595 }
596
597 pub fn meeting_prep() -> Hook {
599 HookBuilder::new("Meeting Prep")
600 .description("Prepare summary 10 minutes before meetings")
601 .trigger(HookTrigger::CalendarEvent {
602 calendar_id: "primary".to_string(),
603 event_type: CalendarEventType::Starting,
604 minutes_before: Some(10),
605 })
606 .action(HookAction::RunAgent {
607 agent_name: "researcher".to_string(),
608 input: "Research the attendees and prepare talking points for this meeting"
609 .to_string(),
610 context: None,
611 })
612 .action(HookAction::Notification {
613 title: "Meeting in 10 minutes".to_string(),
614 body: "Prep notes ready".to_string(),
615 sound: None,
616 actions: None,
617 })
618 .build()
619 }
620
621 pub fn email_auto_respond() -> Hook {
623 HookBuilder::new("Email Auto-Respond")
624 .description("Draft responses to emails matching criteria")
625 .trigger(HookTrigger::EmailReceived {
626 account: "primary".to_string(),
627 filters: Some(EmailFilters {
628 from: None,
629 subject_contains: None,
630 has_attachment: None,
631 labels: Some(vec!["needs-response".to_string()]),
632 }),
633 })
634 .action(HookAction::RunAgent {
635 agent_name: "writer".to_string(),
636 input: "Draft a professional response to this email".to_string(),
637 context: None,
638 })
639 .build()
640 }
641
642 pub fn code_review_reminder() -> Hook {
644 HookBuilder::new("Code Review Reminder")
645 .description("Remind about pending code reviews")
646 .trigger(HookTrigger::Schedule {
647 cron: "0 10,15 * * 1-5".to_string(),
648 timezone: None,
649 })
650 .action(HookAction::HttpRequest {
651 method: "GET".to_string(),
652 url: "https://api.github.com/user/repos".to_string(),
653 headers: None,
654 body: None,
655 })
656 .action(HookAction::Notification {
657 title: "Code Reviews".to_string(),
658 body: "You have pending reviews".to_string(),
659 sound: None,
660 actions: Some(vec!["Open GitHub".to_string()]),
661 })
662 .build()
663 }
664
665 pub fn goodnight_routine() -> Hook {
667 HookBuilder::new("Goodnight Routine")
668 .description("Turn off lights and set thermostat at bedtime")
669 .trigger(HookTrigger::Daily {
670 times: vec!["23:00".to_string()],
671 })
672 .condition(HookCondition::TimeWindow {
673 start: "22:00".to_string(),
674 end: "01:00".to_string(),
675 days: None,
676 })
677 .action(HookAction::SetScene {
678 scene_id: "goodnight".to_string(),
679 })
680 .action(HookAction::ControlDevice {
681 device_id: "thermostat".to_string(),
682 action: "set_temperature".to_string(),
683 parameters: Some(
684 [("temperature".to_string(), serde_json::json!(68))]
685 .into_iter()
686 .collect(),
687 ),
688 })
689 .build()
690 }
691
692 pub fn daily_backup() -> Hook {
694 HookBuilder::new("Daily Backup")
695 .description("Backup important directories daily")
696 .trigger(HookTrigger::Schedule {
697 cron: "0 2 * * *".to_string(),
698 timezone: None,
699 })
700 .action(HookAction::SyncFolder {
701 source: "~/Documents".to_string(),
702 destination: "~/Backups/Documents".to_string(),
703 delete_extra: false,
704 })
705 .action(HookAction::SyncFolder {
706 source: "~/Projects".to_string(),
707 destination: "~/Backups/Projects".to_string(),
708 delete_extra: false,
709 })
710 .action(HookAction::Log {
711 level: "info".to_string(),
712 message: "Daily backup completed".to_string(),
713 })
714 .build()
715 }
716
717 pub fn focus_mode() -> Hook {
719 HookBuilder::new("Focus Mode")
720 .description("Block distractions and notify status")
721 .trigger(HookTrigger::Manual)
722 .action(HookAction::SlackMessage {
723 channel: "#status".to_string(),
724 text: "[Focus] In focus mode - will respond later".to_string(),
725 thread_ts: None,
726 })
727 .action(HookAction::RunCommand {
728 command: "osascript".to_string(),
729 args: vec![
730 "-e".to_string(),
731 "tell application \"System Events\" to set do not disturb to true".to_string(),
732 ],
733 cwd: None,
734 env: None,
735 })
736 .build()
737 }
738
739 pub fn expense_tracker() -> Hook {
741 HookBuilder::new("Expense Tracker")
742 .description("Log expenses from receipts")
743 .trigger(HookTrigger::EmailReceived {
744 account: "primary".to_string(),
745 filters: Some(EmailFilters {
746 from: None,
747 subject_contains: Some("receipt".to_string()),
748 has_attachment: Some(true),
749 labels: None,
750 }),
751 })
752 .action(HookAction::ExtractData {
753 content: "${email.body}".to_string(),
754 schema: serde_json::json!({
755 "vendor": "string",
756 "amount": "number",
757 "date": "date",
758 "category": "string"
759 }),
760 })
761 .build()
762 }
763
764 pub fn all() -> Vec<Hook> {
766 vec![
767 morning_briefing(),
768 meeting_prep(),
769 email_auto_respond(),
770 code_review_reminder(),
771 goodnight_routine(),
772 daily_backup(),
773 focus_mode(),
774 expense_tracker(),
775 ]
776 }
777}