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
)