1use mxr_core::id::*;
2use mxr_core::types::*;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct IpcMessage {
7 pub id: u64,
8 pub payload: IpcPayload,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(tag = "type")]
13#[allow(clippy::large_enum_variant)]
14pub enum IpcPayload {
15 Request(Request),
16 Response(Response),
17 Event(DaemonEvent),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "cmd")]
22pub enum Request {
23 ListEnvelopes {
24 label_id: Option<LabelId>,
25 account_id: Option<AccountId>,
26 limit: u32,
27 offset: u32,
28 },
29 ListEnvelopesByIds {
30 message_ids: Vec<MessageId>,
31 },
32 GetEnvelope {
33 message_id: MessageId,
34 },
35 GetBody {
36 message_id: MessageId,
37 },
38 DownloadAttachment {
39 message_id: MessageId,
40 attachment_id: AttachmentId,
41 },
42 OpenAttachment {
43 message_id: MessageId,
44 attachment_id: AttachmentId,
45 },
46 ListBodies {
47 message_ids: Vec<MessageId>,
48 },
49 GetThread {
50 thread_id: ThreadId,
51 },
52 ListLabels {
53 account_id: Option<AccountId>,
54 },
55 CreateLabel {
56 name: String,
57 color: Option<String>,
58 account_id: Option<AccountId>,
59 },
60 DeleteLabel {
61 name: String,
62 account_id: Option<AccountId>,
63 },
64 RenameLabel {
65 old: String,
66 new: String,
67 account_id: Option<AccountId>,
68 },
69 ListRules,
70 ListAccounts,
71 ListAccountsConfig,
72 AuthorizeAccountConfig {
73 account: AccountConfigData,
74 reauthorize: bool,
75 },
76 UpsertAccountConfig {
77 account: AccountConfigData,
78 },
79 SetDefaultAccount {
80 key: String,
81 },
82 TestAccountConfig {
83 account: AccountConfigData,
84 },
85 GetRule {
86 rule: String,
87 },
88 GetRuleForm {
89 rule: String,
90 },
91 UpsertRule {
92 rule: serde_json::Value,
93 },
94 UpsertRuleForm {
95 existing_rule: Option<String>,
96 name: String,
97 condition: String,
98 action: String,
99 priority: i32,
100 enabled: bool,
101 },
102 DeleteRule {
103 rule: String,
104 },
105 DryRunRules {
106 rule: Option<String>,
107 all: bool,
108 after: Option<String>,
109 },
110 ListEvents {
111 limit: u32,
112 level: Option<String>,
113 category: Option<String>,
114 },
115 GetLogs {
116 limit: u32,
117 level: Option<String>,
118 },
119 GetDoctorReport,
120 GenerateBugReport {
121 verbose: bool,
122 full_logs: bool,
123 since: Option<String>,
124 },
125 ListRuleHistory {
126 rule: Option<String>,
127 limit: u32,
128 },
129 Search {
130 query: String,
131 limit: u32,
132 #[serde(default)]
133 offset: u32,
134 mode: Option<SearchMode>,
135 #[serde(default)]
136 sort: Option<SortOrder>,
137 explain: bool,
138 },
139 SyncNow {
140 account_id: Option<AccountId>,
141 },
142 GetSyncStatus {
143 account_id: AccountId,
144 },
145 SetFlags {
146 message_id: MessageId,
147 flags: MessageFlags,
148 },
149 Count {
150 query: String,
151 mode: Option<SearchMode>,
152 },
153 GetHeaders {
154 message_id: MessageId,
155 },
156 ListSavedSearches,
157 ListSubscriptions {
158 limit: u32,
159 },
160 GetSemanticStatus,
161 EnableSemantic {
162 enabled: bool,
163 },
164 InstallSemanticProfile {
165 profile: SemanticProfile,
166 },
167 UseSemanticProfile {
168 profile: SemanticProfile,
169 },
170 ReindexSemantic,
171 CreateSavedSearch {
172 name: String,
173 query: String,
174 search_mode: SearchMode,
175 },
176 DeleteSavedSearch {
177 name: String,
178 },
179 RunSavedSearch {
180 name: String,
181 limit: u32,
182 },
183 Mutation(MutationCommand),
185 Unsubscribe {
186 message_id: MessageId,
187 },
188 Snooze {
189 message_id: MessageId,
190 wake_at: chrono::DateTime<chrono::Utc>,
191 },
192 Unsnooze {
193 message_id: MessageId,
194 },
195 ListSnoozed,
196 PrepareReply {
198 message_id: MessageId,
199 reply_all: bool,
200 },
201 PrepareForward {
202 message_id: MessageId,
203 },
204 SendDraft {
205 draft: Draft,
206 },
207 SaveDraftToServer {
209 draft: Draft,
210 },
211 ListDrafts,
212
213 ExportThread {
215 thread_id: ThreadId,
216 format: ExportFormat,
217 },
218 ExportSearch {
219 query: String,
220 format: ExportFormat,
221 },
222
223 GetStatus,
224 Ping,
225 Shutdown,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(tag = "mutation")]
231pub enum MutationCommand {
232 Archive {
233 message_ids: Vec<MessageId>,
234 },
235 ReadAndArchive {
236 message_ids: Vec<MessageId>,
237 },
238 Trash {
239 message_ids: Vec<MessageId>,
240 },
241 Spam {
242 message_ids: Vec<MessageId>,
243 },
244 Star {
245 message_ids: Vec<MessageId>,
246 starred: bool,
247 },
248 SetRead {
249 message_ids: Vec<MessageId>,
250 read: bool,
251 },
252 ModifyLabels {
253 message_ids: Vec<MessageId>,
254 add: Vec<String>,
255 remove: Vec<String>,
256 },
257 Move {
258 message_ids: Vec<MessageId>,
259 target_label: String,
260 },
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct ReplyContext {
266 pub in_reply_to: String,
267 pub references: Vec<String>,
268 pub reply_to: String,
269 pub cc: String,
270 pub subject: String,
271 pub from: String,
272 pub thread_context: String,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct ForwardContext {
278 pub subject: String,
279 pub from: String,
280 pub forwarded_content: String,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
284#[serde(tag = "status")]
285#[allow(clippy::large_enum_variant)]
286pub enum Response {
287 Ok { data: ResponseData },
288 Error { message: String },
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
292#[serde(tag = "kind")]
293#[allow(clippy::large_enum_variant)]
294pub enum ResponseData {
295 Envelopes {
296 envelopes: Vec<Envelope>,
297 },
298 Envelope {
299 envelope: Envelope,
300 },
301 Body {
302 body: MessageBody,
303 },
304 AttachmentFile {
305 file: AttachmentFile,
306 },
307 Bodies {
308 bodies: Vec<MessageBody>,
309 },
310 Thread {
311 thread: Thread,
312 messages: Vec<Envelope>,
313 },
314 Labels {
315 labels: Vec<Label>,
316 },
317 Label {
318 label: Label,
319 },
320 Rules {
321 rules: Vec<serde_json::Value>,
322 },
323 RuleData {
324 rule: serde_json::Value,
325 },
326 Accounts {
327 accounts: Vec<AccountSummaryData>,
328 },
329 AccountsConfig {
330 accounts: Vec<AccountConfigData>,
331 },
332 AccountOperation {
333 result: AccountOperationResult,
334 },
335 RuleFormData {
336 form: RuleFormData,
337 },
338 RuleDryRun {
339 results: Vec<serde_json::Value>,
340 },
341 EventLogEntries {
342 entries: Vec<EventLogEntry>,
343 },
344 LogLines {
345 lines: Vec<String>,
346 },
347 DoctorReport {
348 report: DoctorReport,
349 },
350 BugReport {
351 content: String,
352 },
353 RuleHistory {
354 entries: Vec<serde_json::Value>,
355 },
356 SearchResults {
357 results: Vec<SearchResultItem>,
358 #[serde(default)]
359 has_more: bool,
360 explain: Option<SearchExplain>,
361 },
362 SyncStatus {
363 sync: AccountSyncStatus,
364 },
365 Count {
366 count: u32,
367 },
368 Headers {
369 headers: Vec<(String, String)>,
370 },
371 SavedSearches {
372 searches: Vec<mxr_core::types::SavedSearch>,
373 },
374 Subscriptions {
375 subscriptions: Vec<mxr_core::types::SubscriptionSummary>,
376 },
377 SemanticStatus {
378 snapshot: SemanticStatusSnapshot,
379 },
380 SavedSearchData {
381 search: mxr_core::types::SavedSearch,
382 },
383 Status {
384 uptime_secs: u64,
385 accounts: Vec<String>,
386 total_messages: u32,
387 #[serde(default)]
388 daemon_pid: Option<u32>,
389 #[serde(default)]
390 sync_statuses: Vec<AccountSyncStatus>,
391 #[serde(default)]
392 protocol_version: u32,
393 #[serde(default)]
394 daemon_version: Option<String>,
395 #[serde(default)]
396 daemon_build_id: Option<String>,
397 #[serde(default)]
398 repair_required: bool,
399 },
400 ReplyContext {
401 context: ReplyContext,
402 },
403 ForwardContext {
404 context: ForwardContext,
405 },
406 Drafts {
407 drafts: Vec<Draft>,
408 },
409 SnoozedMessages {
410 snoozed: Vec<Snoozed>,
411 },
412 ExportResult {
413 content: String,
414 },
415 Pong,
416 Ack,
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct SearchResultItem {
421 pub message_id: MessageId,
422 pub account_id: AccountId,
423 pub thread_id: ThreadId,
424 pub score: f32,
425 pub mode: SearchMode,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct SearchExplain {
430 pub requested_mode: SearchMode,
431 pub executed_mode: SearchMode,
432 pub semantic_query: Option<String>,
433 pub lexical_window: u32,
434 pub dense_window: Option<u32>,
435 pub lexical_candidates: u32,
436 pub dense_candidates: u32,
437 pub final_results: u32,
438 pub rrf_k: Option<u32>,
439 pub notes: Vec<String>,
440 pub results: Vec<SearchExplainResult>,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct SearchExplainResult {
445 pub rank: u32,
446 pub message_id: MessageId,
447 pub final_score: f32,
448 pub lexical_rank: Option<u32>,
449 pub lexical_score: Option<f32>,
450 pub dense_rank: Option<u32>,
451 pub dense_score: Option<f32>,
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize)]
455pub struct AttachmentFile {
456 pub attachment_id: AttachmentId,
457 pub filename: String,
458 pub path: String,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
462pub struct EventLogEntry {
463 pub timestamp: i64,
464 pub level: String,
465 pub category: String,
466 pub account_id: Option<AccountId>,
467 pub message_id: Option<String>,
468 pub rule_id: Option<String>,
469 pub summary: String,
470 pub details: Option<String>,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
474#[serde(rename_all = "snake_case")]
475pub enum DaemonHealthClass {
476 #[default]
477 Healthy,
478 Degraded,
479 RestartRequired,
480 RepairRequired,
481}
482
483impl DaemonHealthClass {
484 pub fn as_str(&self) -> &'static str {
485 match self {
486 Self::Healthy => "healthy",
487 Self::Degraded => "degraded",
488 Self::RestartRequired => "restart_required",
489 Self::RepairRequired => "repair_required",
490 }
491 }
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
495#[serde(rename_all = "snake_case")]
496pub enum IndexFreshness {
497 #[default]
498 Unknown,
499 Current,
500 Stale,
501 Disabled,
502 Indexing,
503 Error,
504 RepairRequired,
505}
506
507impl IndexFreshness {
508 pub fn as_str(&self) -> &'static str {
509 match self {
510 Self::Unknown => "unknown",
511 Self::Current => "current",
512 Self::Stale => "stale",
513 Self::Disabled => "disabled",
514 Self::Indexing => "indexing",
515 Self::Error => "error",
516 Self::RepairRequired => "repair_required",
517 }
518 }
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
522pub struct AccountSyncStatus {
523 pub account_id: AccountId,
524 pub account_name: String,
525 pub last_attempt_at: Option<String>,
526 pub last_success_at: Option<String>,
527 pub last_error: Option<String>,
528 pub failure_class: Option<String>,
529 pub consecutive_failures: u32,
530 pub backoff_until: Option<String>,
531 pub sync_in_progress: bool,
532 pub current_cursor_summary: Option<String>,
533 pub last_synced_count: u32,
534 pub healthy: bool,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
538pub struct DoctorReport {
539 pub healthy: bool,
540 #[serde(default)]
541 pub health_class: DaemonHealthClass,
542 #[serde(default)]
543 pub lexical_index_freshness: IndexFreshness,
544 #[serde(default)]
545 pub last_successful_sync_at: Option<String>,
546 #[serde(default)]
547 pub lexical_last_rebuilt_at: Option<String>,
548 #[serde(default)]
549 pub semantic_enabled: bool,
550 #[serde(default)]
551 pub semantic_active_profile: Option<String>,
552 #[serde(default)]
553 pub semantic_index_freshness: IndexFreshness,
554 #[serde(default)]
555 pub semantic_last_indexed_at: Option<String>,
556 #[serde(default)]
557 pub data_stats: DoctorDataStats,
558 pub data_dir_exists: bool,
559 pub database_exists: bool,
560 pub index_exists: bool,
561 pub socket_exists: bool,
562 pub socket_reachable: bool,
563 pub stale_socket: bool,
564 pub daemon_running: bool,
565 pub daemon_pid: Option<u32>,
566 #[serde(default)]
567 pub daemon_protocol_version: u32,
568 #[serde(default)]
569 pub daemon_version: Option<String>,
570 #[serde(default)]
571 pub daemon_build_id: Option<String>,
572 pub index_lock_held: bool,
573 pub index_lock_error: Option<String>,
574 #[serde(default)]
575 pub restart_required: bool,
576 #[serde(default)]
577 pub repair_required: bool,
578 pub database_path: String,
579 pub database_size_bytes: u64,
580 pub index_path: String,
581 pub index_size_bytes: u64,
582 pub log_path: String,
583 pub log_size_bytes: u64,
584 pub sync_statuses: Vec<AccountSyncStatus>,
585 pub recent_sync_events: Vec<EventLogEntry>,
586 pub recent_error_logs: Vec<String>,
587 pub recommended_next_steps: Vec<String>,
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize, Default)]
591pub struct DoctorDataStats {
592 pub accounts: u32,
593 pub labels: u32,
594 pub messages: u32,
595 pub unread_messages: u32,
596 pub starred_messages: u32,
597 pub messages_with_attachments: u32,
598 pub message_labels: u32,
599 pub bodies: u32,
600 pub attachments: u32,
601 pub drafts: u32,
602 pub snoozed: u32,
603 pub saved_searches: u32,
604 pub rules: u32,
605 pub rule_logs: u32,
606 pub sync_log: u32,
607 pub sync_runtime_statuses: u32,
608 pub event_log: u32,
609 pub semantic_profiles: u32,
610 pub semantic_chunks: u32,
611 pub semantic_embeddings: u32,
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
615pub struct RuleFormData {
616 pub id: Option<String>,
617 pub name: String,
618 pub condition: String,
619 pub action: String,
620 pub priority: i32,
621 pub enabled: bool,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct AccountConfigData {
626 pub key: String,
627 pub name: String,
628 pub email: String,
629 pub sync: Option<AccountSyncConfigData>,
630 pub send: Option<AccountSendConfigData>,
631 pub is_default: bool,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
635#[serde(rename_all = "snake_case")]
636pub enum AccountSourceData {
637 Runtime,
638 Config,
639 Both,
640}
641
642#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
643#[serde(rename_all = "snake_case")]
644pub enum AccountEditModeData {
645 Full,
646 RuntimeOnly,
647}
648
649#[derive(Debug, Clone, Serialize, Deserialize)]
650pub struct AccountSummaryData {
651 pub account_id: AccountId,
652 pub key: Option<String>,
653 pub name: String,
654 pub email: String,
655 pub provider_kind: String,
656 pub sync_kind: Option<String>,
657 pub send_kind: Option<String>,
658 pub enabled: bool,
659 pub is_default: bool,
660 pub source: AccountSourceData,
661 pub editable: AccountEditModeData,
662 pub sync: Option<AccountSyncConfigData>,
663 pub send: Option<AccountSendConfigData>,
664}
665
666#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
667#[serde(rename_all = "snake_case")]
668pub enum GmailCredentialSourceData {
669 #[default]
670 Bundled,
671 Custom,
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize)]
675#[serde(tag = "type", rename_all = "snake_case")]
676pub enum AccountSyncConfigData {
677 Gmail {
678 #[serde(default)]
679 credential_source: GmailCredentialSourceData,
680 client_id: String,
681 client_secret: Option<String>,
682 token_ref: String,
683 },
684 Imap {
685 host: String,
686 port: u16,
687 username: String,
688 password_ref: String,
689 password: Option<String>,
690 use_tls: bool,
691 },
692}
693
694#[derive(Debug, Clone, Serialize, Deserialize)]
695pub struct AccountOperationStep {
696 pub ok: bool,
697 pub detail: String,
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
701pub struct AccountOperationResult {
702 pub ok: bool,
703 pub summary: String,
704 pub save: Option<AccountOperationStep>,
705 pub auth: Option<AccountOperationStep>,
706 pub sync: Option<AccountOperationStep>,
707 pub send: Option<AccountOperationStep>,
708}
709
710#[derive(Debug, Clone, Serialize, Deserialize)]
711#[serde(tag = "type", rename_all = "snake_case")]
712pub enum AccountSendConfigData {
713 Gmail,
714 Smtp {
715 host: String,
716 port: u16,
717 username: String,
718 password_ref: String,
719 password: Option<String>,
720 use_tls: bool,
721 },
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize)]
725#[serde(tag = "event")]
726pub enum DaemonEvent {
727 SyncCompleted {
728 account_id: AccountId,
729 messages_synced: u32,
730 },
731 SyncError {
732 account_id: AccountId,
733 error: String,
734 },
735 NewMessages {
736 envelopes: Vec<Envelope>,
737 },
738 MessageUnsnoozed {
739 message_id: MessageId,
740 },
741 LabelCountsUpdated {
742 counts: Vec<LabelCount>,
743 },
744}
745
746#[derive(Debug, Clone, Serialize, Deserialize)]
747pub struct LabelCount {
748 pub label_id: LabelId,
749 pub unread_count: u32,
750 pub total_count: u32,
751}