car-integrations 0.30.0

OS-native account-bound integrations (Calendar, Contacts, Mail) for CAR
Documentation
//! Account-bound integrations for Common Agent Runtime: Calendar, Contacts,
//! Mail, Messages.
//!
//! The logical capabilities are the same on every OS; the backends are
//! not. v1 defines the release contract — stable return shapes that carry
//! explicit `available` + `backend` fields so downstream apps can branch
//! cleanly while backends light up incrementally.
//!
//! # Backends (target)
//!
//! | OS      | Calendar                  | Contacts                  | Mail                 | Messages                    |
//! |---------|---------------------------|---------------------------|----------------------|-----------------------------|
//! | macOS   | EventKit (EKEventStore)   | Contacts.framework         | Mail.app automation  | Messages.app automation     |
//! | Windows | MS Graph + Outlook MAPI   | Windows.Contacts + Graph   | MS Graph + MAPI      | Not modeled                 |
//! | Linux   | Evolution DS + CalDAV     | Evolution DS + CardDAV     | Evolution DS + IMAP  | Not modeled                 |
//!
//! Backends return Unavailable-with-reason so callers can distinguish missing
//! OS configuration, denied TCC access, and unmodeled platforms.
//!
//! # Dependencies (and honest gaps)
//!
//! Full operation needs:
//! - **car-secrets** — where credentials and tokens live
//! - **car-accounts** — to know which account the call should be bound to
//! - **car-permissions** — to preflight OS consent before the side effect
//!
//! The macOS backends compose these where the OS exposes a useful native
//! surface; other platforms are still modeled but not fully implemented.

use serde::{Deserialize, Serialize};
use thiserror::Error;

pub mod apple;
pub mod calendar;
pub mod contacts;
pub mod mail;
pub mod messages;

/// Availability envelope — returned from every list-ish method so callers
/// can branch on `available` instead of assuming a populated list.
///
/// **Flatten caution.** This struct is `#[serde(flatten)]`ed into every
/// listing payload (see `CalendarListing`, `ContactListing`, etc.). Do not
/// add fields with names that collide with payload fields. `#[non_exhaustive]`
/// to keep future additions additive.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Availability {
    /// True if the backend returned real data.
    pub available: bool,
    /// Backend that was attempted (`"eventkit"`, `"msgraph"`, `"eds"`, ...).
    pub backend: String,
    /// When `available` is false, human-readable explanation.
    pub reason: Option<String>,
}

impl Availability {
    pub fn pending(backend: &'static str, reason: impl Into<String>) -> Self {
        Self {
            available: false,
            backend: backend.to_string(),
            reason: Some(reason.into()),
        }
    }

    pub fn available(backend: &'static str) -> Self {
        Self {
            available: true,
            backend: backend.to_string(),
            reason: None,
        }
    }
}

#[derive(Debug, Error)]
pub enum IntegrationError {
    #[error("backend not available: {0}")]
    Unavailable(String),
    #[error("integration backend error: {0}")]
    Backend(String),
    #[error("not yet implemented: {0}")]
    NotImplemented(&'static str),
}