car_integrations/lib.rs
1//! Account-bound integrations for Common Agent Runtime: Calendar, Contacts,
2//! Mail, Messages.
3//!
4//! The logical capabilities are the same on every OS; the backends are
5//! not. v1 defines the release contract — stable return shapes that carry
6//! explicit `available` + `backend` fields so downstream apps can branch
7//! cleanly while backends light up incrementally.
8//!
9//! # Backends (target)
10//!
11//! | OS | Calendar | Contacts | Mail | Messages |
12//! |---------|---------------------------|---------------------------|----------------------|-----------------------------|
13//! | macOS | EventKit (EKEventStore) | Contacts.framework | Mail.app automation | Messages.app automation |
14//! | Windows | MS Graph + Outlook MAPI | Windows.Contacts + Graph | MS Graph + MAPI | Not modeled |
15//! | Linux | Evolution DS + CalDAV | Evolution DS + CardDAV | Evolution DS + IMAP | Not modeled |
16//!
17//! Backends return Unavailable-with-reason so callers can distinguish missing
18//! OS configuration, denied TCC access, and unmodeled platforms.
19//!
20//! # Dependencies (and honest gaps)
21//!
22//! Full operation needs:
23//! - **car-secrets** — where credentials and tokens live
24//! - **car-accounts** — to know which account the call should be bound to
25//! - **car-permissions** — to preflight OS consent before the side effect
26//!
27//! The macOS backends compose these where the OS exposes a useful native
28//! surface; other platforms are still modeled but not fully implemented.
29
30use serde::{Deserialize, Serialize};
31use thiserror::Error;
32
33pub mod apple;
34pub mod calendar;
35pub mod contacts;
36// Windows is the only caller today (the local-store-first contacts and
37// calendar reads); `test` keeps the decision under test on every host, since
38// the whole point of splitting it out was to make it testable off Windows.
39#[cfg(target_os = "macos")]
40pub mod jxa;
41#[cfg(any(target_os = "windows", test))]
42pub(crate) mod local_first;
43pub mod mail;
44pub mod messages;
45pub mod msgraph;
46
47/// Availability envelope — returned from every list-ish method so callers
48/// can branch on `available` instead of assuming a populated list.
49///
50/// **Flatten caution.** This struct is `#[serde(flatten)]`ed into every
51/// listing payload (see `CalendarListing`, `ContactListing`, etc.). Do not
52/// add fields with names that collide with payload fields. `#[non_exhaustive]`
53/// to keep future additions additive.
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[non_exhaustive]
56pub struct Availability {
57 /// True if the backend returned real data.
58 pub available: bool,
59 /// Backend that was attempted (`"eventkit"`, `"msgraph"`, `"eds"`, ...).
60 pub backend: String,
61 /// When `available` is false, human-readable explanation.
62 pub reason: Option<String>,
63}
64
65impl Availability {
66 pub fn pending(backend: &'static str, reason: impl Into<String>) -> Self {
67 Self {
68 available: false,
69 backend: backend.to_string(),
70 reason: Some(reason.into()),
71 }
72 }
73
74 pub fn available(backend: &'static str) -> Self {
75 Self {
76 available: true,
77 backend: backend.to_string(),
78 reason: None,
79 }
80 }
81}
82
83#[derive(Debug, Error)]
84pub enum IntegrationError {
85 #[error("backend not available: {0}")]
86 Unavailable(String),
87 #[error("integration backend error: {0}")]
88 Backend(String),
89 #[error("not yet implemented: {0}")]
90 NotImplemented(&'static str),
91}