Plugin API Reference

Complete reference for the Elysium plugin API. Learn about lifecycle hooks, available interfaces, and how to interact with schedule data.

Lifecycle Hooks

Every plugin implements the ElysiumPlugin protocol, which defines lifecycle hooks called by Elysium at appropriate times.

onLoad()

Called when the plugin is loaded. Use this to register commands, views, and set up initial state.

func onLoad() {
    // Register commands, views, data providers
    registerCommand(myCommand)
    registerView(myCustomView)
}

onUnload()

Called when the plugin is disabled or Elysium is quitting. Clean up resources here.

func onUnload() {
    // Cancel subscriptions, save state, clean up
    cancellables.forEach { $0.cancel() }
}

onScheduleChange(_ schedule: Schedule)

Called whenever the schedule changes. Useful for syncing with external services.

func onScheduleChange(_ schedule: Schedule) {
    // React to schedule updates
    for item in schedule.items where item.isModified {
        syncToExternalService(item)
    }
}

Commands

Commands appear in the action menu and can be triggered via keyboard shortcuts. Register them in onLoad().

struct Command {
    let id: String              // Unique identifier
    let name: String            // Display name
    let icon: String            // SF Symbol name
    let shortcut: KeyboardShortcut?  // Optional keyboard shortcut
    let action: (CommandContext) -> Void
}

struct CommandContext {
    let selectedItem: ScheduleItem?   // Currently selected item
    let selectedItems: [ScheduleItem] // All selected items
    let schedule: Schedule            // Current schedule
    let view: ViewContext             // Current view info
}

Example: Add a Command with Shortcut

registerCommand(
    Command(
        id: "my-plugin.archive",
        name: "Archive Item",
        icon: "archivebox",
        shortcut: KeyboardShortcut(.e, modifiers: [.command, .shift]),
        action: { context in
            guard let item = context.selectedItem else { return }
            archiveItem(item)
        }
    )
)

Schedule API

Access and modify schedule data using the Schedule API. All items conform to the OpenTime specification.

Reading Items

// Get all items
let items = schedule.items

// Filter by type
let tasks = schedule.items(ofType: .task)
let goals = schedule.items(ofType: .goal)

// Query with predicates
let overdue = schedule.items.filter {
    $0.due != nil && $0.due! < Date() && $0.status != .done
}

// Get item by ID
if let item = schedule.item(id: "task-123") {
    print(item.title)
}

Creating Items

let newTask = ScheduleItem(
    type: .task,
    title: "Review pull request",
    status: .todo,
    due: Date().addingTimeInterval(86400), // Tomorrow
    tags: ["work", "code-review"]
)

schedule.add(newTask)

Updating Items

// Modify and save
var item = schedule.item(id: "task-123")!
item.status = .done
item.completedAt = Date()
schedule.update(item)

// Batch updates
schedule.batch {
    for item in itemsToArchive {
        var mutable = item
        mutable.archived = true
        schedule.update(mutable)
    }
}

Item Types

enum ItemType {
    case goal        // Long-term objectives with progress
    case task        // Actionable items with status
    case habit       // Recurring behaviors with streaks
    case reminder    // Time-triggered notifications
    case event       // Calendar blocks with start/end
    case appointment // Events with attendees
    case project     // Containers for other items (Odysseys)
}

Custom Views

Create custom SwiftUI views that appear in Elysium's sidebar or as standalone windows.

struct PluginView: View, ElysiumPluginView {
    static let id = "my-plugin.dashboard"
    static let name = "My Dashboard"
    static let icon = "chart.bar"
    static let placement: ViewPlacement = .sidebar

    @EnvironmentObject var schedule: ScheduleStore

    var body: some View {
        VStack {
            Text("Today's Progress")
                .font(.headline)

            let completed = schedule.items(ofType: .task)
                .filter { $0.status == .done }
                .count

            Text("\(completed) tasks completed")
                .foregroundStyle(.secondary)
        }
        .padding()
    }
}

View Placements: .sidebar, .panel, .window, .popover

Settings Storage

Store plugin settings that persist across sessions. Settings are automatically synced if the user has cloud sync enabled.

// Store a value
PluginSettings.set("apiKey", value: "sk-...")
PluginSettings.set("syncEnabled", value: true)

// Retrieve a value
let apiKey: String? = PluginSettings.get("apiKey")
let syncEnabled: Bool = PluginSettings.get("syncEnabled") ?? false

// Remove a value
PluginSettings.remove("apiKey")

// Observe changes
PluginSettings.observe("syncEnabled") { newValue in
    updateSyncState(enabled: newValue as? Bool ?? false)
}

Notifications

Show notifications to users for important events or confirmations.

// Simple notification
ElysiumNotification.show(
    title: "Sync Complete",
    message: "12 items synced successfully"
)

// With action
ElysiumNotification.show(
    title: "Import Ready",
    message: "Found 24 items to import",
    action: NotificationAction(title: "Import All") {
        performImport()
    }
)

// Error notification
ElysiumNotification.showError(
    title: "Sync Failed",
    message: error.localizedDescription
)