diff --git a/crates/Cargo.toml b/crates/Cargo.toml index 289e4cc3..30db401f 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -15,6 +15,8 @@ members = [ "khive-pack-gtd", "khive-pack-memory", "khive-pack-brain", + "khive-calendar", + "khive-calendar-google", "khive-mcp", "khive-vcs", "kkernel", diff --git a/crates/khive-calendar-google/Cargo.toml b/crates/khive-calendar-google/Cargo.toml new file mode 100644 index 00000000..07463996 --- /dev/null +++ b/crates/khive-calendar-google/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "khive-calendar-google" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +description = "Google Calendar handler — reference khive-calendar implementation (OAuth + Calendar API)" + +[dependencies] +khive-types = { version = "0.2.0", path = "../khive-types", features = ["serde"] } +khive-calendar = { version = "0.2.0", path = "../khive-calendar" } +async-trait = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +chrono = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } diff --git a/crates/khive-calendar-google/src/lib.rs b/crates/khive-calendar-google/src/lib.rs new file mode 100644 index 00000000..5cf244ec --- /dev/null +++ b/crates/khive-calendar-google/src/lib.rs @@ -0,0 +1,14 @@ +//! khive-calendar-google — Google Calendar reference implementation of [`khive_calendar::CalendarHandler`]. +//! +//! Demonstrates: OAuth flow, event create/update/cancel, recurrence handling, +//! availability query, sync with the GTD-owned native calendar. +//! +//! Reference implementation for community to model future calendar +//! integrations on (Apple/iCloud, Outlook, CalDAV, ...). +//! +//! **OSS-quality reference**, NOT the gated version Cloud sells. The Cloud +//! offering will wrap a separate, formal-gate-enforced implementation with +//! compliance + audit guarantees on top of the same trait surface. +//! +//! Scaffold only — auth flow, event lifecycle, and recurrence handling are +//! deferred (see `.khive/notes/strategy_20260522_open_core_split.md`). diff --git a/crates/khive-calendar/Cargo.toml b/crates/khive-calendar/Cargo.toml new file mode 100644 index 00000000..c4415260 --- /dev/null +++ b/crates/khive-calendar/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "khive-calendar" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +description = "Calendar abstraction — the CalendarHandler trait and event envelope types consumed by khive-pack-gtd" + +[dependencies] +khive-types = { version = "0.2.0", path = "../khive-types", features = ["serde"] } +async-trait = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +chrono = { workspace = true } +uuid = { workspace = true } diff --git a/crates/khive-calendar/docs/reference/cal_bridge.applescript b/crates/khive-calendar/docs/reference/cal_bridge.applescript new file mode 100644 index 00000000..d603c67b --- /dev/null +++ b/crates/khive-calendar/docs/reference/cal_bridge.applescript @@ -0,0 +1,151 @@ +-- cal_bridge.applescript +-- Reads events from Calendar.app in a date range and outputs JSON to stdout. +-- Usage: +-- osascript cal_bridge.applescript +-- osascript cal_bridge.applescript 7 +-- osascript cal_bridge.applescript 7 30 +-- +-- Parameters: +-- argv[1] : days_back (default 7) +-- argv[2] : days_forward (default 30) +-- +-- Output: JSON array of event objects. + +on escapeJSON(rawText) + set t to rawText + set t to my replaceText(t, "\\", "\\\\") + set t to my replaceText(t, "\"", "\\\"") + set t to my replaceText(t, "/", "\\/") + set t to my replaceText(t, return, "\\n") + set t to my replaceText(t, linefeed, "\\n") + set t to my replaceText(t, tab, "\\t") + return t +end escapeJSON + +on replaceText(theText, searchStr, replacementStr) + set AppleScript's text item delimiters to searchStr + set textItems to text items of theText + set AppleScript's text item delimiters to replacementStr + set result to textItems as string + set AppleScript's text item delimiters to "" + return result +end replaceText + +on dateToEpoch(d) + return (d - (date "Sunday, January 1, 2001 at 12:00:00 AM")) + 978307200 +end dateToEpoch + +on joinList(theList, delimiter) + set AppleScript's text item delimiters to delimiter + set result to theList as string + set AppleScript's text item delimiters to "" + return result +end joinList + +on run argv + set daysBack to 7 + set daysForward to 30 + + if (count of argv) >= 1 then + try + set daysBack to item 1 of argv as integer + end try + end if + if (count of argv) >= 2 then + try + set daysForward to item 2 of argv as integer + end try + end if + + set rangeStart to (current date) - (daysBack * days) + set rangeEnd to (current date) + (daysForward * days) + + -- Zero out time on range boundaries for cleaner filtering. + set time of rangeStart to 0 + set time of rangeEnd to 86399 -- end of day + + set jsonParts to {} + + tell application "Calendar" + set allCals to every calendar + + repeat with cal in allCals + set calName to name of cal + + try + -- events between startDate and endDate + set calEvents to (every event of cal whose start date >= rangeStart and start date <= rangeEnd) + + repeat with evt in calEvents + try + set evtUID to uid of evt + if evtUID is missing value then set evtUID to "" + + set evtTitle to summary of evt + if evtTitle is missing value then set evtTitle to "" + + set evtStart to start date of evt + set evtEnd to end date of evt + set startEpoch to my dateToEpoch(evtStart) + set endEpoch to my dateToEpoch(evtEnd) + + set evtAllDay to allday event of evt + set allDayStr to "false" + if evtAllDay then set allDayStr to "true" + + set evtLocation to "" + try + set evtLocation to location of evt + if evtLocation is missing value then set evtLocation to "" + end try + + set evtNotes to "" + try + set evtNotes to description of evt + if evtNotes is missing value then set evtNotes to "" + end try + + set evtRecurrence to "" + try + set evtRecurrence to recurrence of evt + if evtRecurrence is missing value then set evtRecurrence to "" + end try + + -- Attendee emails: use email property (name property throws errors). + set attendeeList to {} + try + set evtAttendees to attendees of evt + repeat with att in evtAttendees + try + set attEmail to email of att + if attEmail is not missing value and attEmail is not "" then + set end of attendeeList to "\"" & my escapeJSON(attEmail) & "\"" + end if + end try + end repeat + end try + set attendeesJSON to "[" & my joinList(attendeeList, ",") & "]" + + set jsonObj to "{" & ¬ + "\"uid\":\"" & my escapeJSON(evtUID) & "\"," & ¬ + "\"title\":\"" & my escapeJSON(evtTitle) & "\"," & ¬ + "\"start_date\":" & startEpoch & "," & ¬ + "\"end_date\":" & endEpoch & "," & ¬ + "\"calendar\":\"" & my escapeJSON(calName) & "\"," & ¬ + "\"location\":\"" & my escapeJSON(evtLocation) & "\"," & ¬ + "\"notes\":\"" & my escapeJSON(evtNotes) & "\"," & ¬ + "\"all_day\":" & allDayStr & "," & ¬ + "\"recurrence\":\"" & my escapeJSON(evtRecurrence) & "\"," & ¬ + "\"attendee_emails\":" & attendeesJSON & ¬ + "}" + + set end of jsonParts to jsonObj + end try + end repeat + end try + end repeat + end tell + + set jsonOutput to "[" & my joinList(jsonParts, ",") & "]" + return jsonOutput +end run diff --git a/crates/khive-calendar/src/lib.rs b/crates/khive-calendar/src/lib.rs new file mode 100644 index 00000000..f7428f44 --- /dev/null +++ b/crates/khive-calendar/src/lib.rs @@ -0,0 +1,17 @@ +//! khive-calendar — calendar abstraction layer. +//! +//! Defines the [`CalendarHandler`] trait that concrete calendar crates +//! (`khive-calendar-native`, `khive-calendar-google`, `khive-calendar-apple`, ...) +//! implement. Consumed by `khive-pack-gtd` to schedule tasks, surface +//! deadlines, and roundtrip events between khive's GTD model and the agent's +//! external calendar(s). +//! +//! Mirrors the [`khive_channel::ChannelHandler`] pattern used by +//! `khive-pack-comm`. The same open-core split applies: OSS provides the +//! trait + reference impls; Cloud provides gated, audited, capability-scoped +//! hosted calendar services. +//! +//! Scaffold only — detailed trait surface (event create/update/cancel, +//! availability query, recurrence model, RFC 5545 mapping) and registration +//! mechanism are deferred (see +//! `.khive/notes/strategy_20260522_open_core_split.md`).