Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ members = [
"khive-pack-gtd",
"khive-pack-memory",
"khive-pack-brain",
"khive-calendar",
"khive-calendar-google",
"khive-mcp",
"khive-vcs",
"kkernel",
Expand Down
22 changes: 22 additions & 0 deletions crates/khive-calendar-google/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
14 changes: 14 additions & 0 deletions crates/khive-calendar-google/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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`).
20 changes: 20 additions & 0 deletions crates/khive-calendar/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
151 changes: 151 additions & 0 deletions crates/khive-calendar/docs/reference/cal_bridge.applescript
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions crates/khive-calendar/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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`).
Loading