Skip to content

stoicapp/StoicKit

Repository files navigation

StoicKit

⚠️ Not viable on iOS 26 — do not integrate. The cross-app ExtensionKit architecture StoicKit is built on is gated behind a private Apple entitlement as of iOS 26 beta 3+. AppExtensionPoint.Monitor returns zero identities for extension points whose provider lives in a different app, regardless of configuration, App Group, or shared Team ID. See BLOCKER.md for the full writeup and forward options (App Intents, Share Extension, etc.). This repo is kept as a reference; the public API documented below is preserved for historical context.

A Swift package for writing journal entries into Stoic from any app.

StoicKit lets partner apps push journal entries into Stoic in real time through Apple's ExtensionKit framework. Call store.save(entry) from your app — StoicKit handles delivery automatically.

Runtime requires iOS 26+ / macOS 26+. The package links on iOS 15+ / macOS 12+ so apps with a lower deployment target can depend on it and gate usage with if #available(iOS 26, macOS 26, *). Built with Swift 6.2 (Xcode 26+).

How It Works

┌────────────────┐  App Group    ┌────────────────┐   XPC   ┌──────────┐
│    Your App    │──────────────►│   Relay Ext    │────────►│  Stoic   │
│                │   + Darwin    │  (StoicRelay)  │         │          │
│  store.save()  │  notification │  auto-forward  │         │  receive │
└────────────────┘               └────────────────┘         └──────────┘
  1. Your app calls store.save(entry) — writes to the App Group shared container.
  2. A Darwin notification wakes the relay extension.
  3. The relay forwards the entry to Stoic over XPC.

If Stoic is not connected, entries queue in the shared container and deliver on next connection.

Quick Start

1. Add the dependency

dependencies: [
    .package(url: "https://github.com/stoicapp/StoicKit", from: "0.1.0")
]

2. Save entries from your app

import StoicKit

let store = StoicStore(appGroup: "group.com.yourcompany.yourapp")
try store.save(JournalEntry(text: "Today I practiced mindfulness."))

3. Create the relay extension

Add a new target: File > New > Target > App Extension > Generic Extension

Set EXExtensionPointIdentifier in its Info.plist:

<key>EXAppExtensionAttributes</key>
<dict>
    <key>EXExtensionPointIdentifier</key>
    <string>com.getstoic.relay</string>
</dict>

The entire extension is one file:

import StoicKit

@main
struct MyStoicRelay: StoicRelay {
    static let appGroup = "group.com.yourcompany.yourapp"
}

Make sure both your app and extension share the same App Group in their entitlements.

Delivery Behavior

Entries saved via StoicStore.save() are queued in the App Group shared container and delivered to Stoic as soon as possible. Delivery timing depends on Stoic's connection state:

Stoic state Delivery How
Running (foreground/suspended) Instant Stoic keeps persistent connections to all enabled extensions. The Darwin notification wakes the relay, which forwards immediately over the existing XPC connection.
Not running Next background refresh (~15 min) Stoic periodically wakes via background app refresh, connects to extensions, and drains pending queues.

Entries are never lost — they persist in the shared container until successfully delivered. If delivery fails (e.g., Stoic rejects the entry), the entry remains queued and retries on the next sync.

You do not need to handle connection state, retries, or delivery timing. Just call store.save() and StoicKit takes care of the rest.

Data Model

JournalEntry

Intentionally minimal. Will be expanded as the API evolves.

JournalEntry(
    id: UUID(),           // Unique identifier for deduplication
    text: "Journal text", // Entry content
    timestamp: .now,      // Creation date
    modifiedAt: nil       // Last modification date (optional)
)

Agent Setup Prompt

Copy this prompt into your AI coding agent to scaffold the integration automatically:

Add StoicKit integration to this project. Add the StoicKit Swift package from https://github.com/stoicapp/StoicKit. Check if the project already has an App Group configured — if so, ask me which one to use; if not, create one and share it between the main app and a new Generic Extension target. In the main app, create a StoicStore(appGroup:) and call store.save(JournalEntry(text:)) after the user finishes writing a journal entry. For the extension target, set EXExtensionPointIdentifier to com.getstoic.relay in its Info.plist and create a single-file @main struct conforming to StoicRelay with the matching appGroup. Both targets must share the same App Group in their entitlements.

Example

See Example/ for complete reference implementations:

  • ExampleApp.swift — Main app calling store.save()
  • ExampleRelay.swift — The relay extension (one file)

Requirements

  • Runtime: iOS 26.0+ / macOS 26.0+ — all public APIs (StoicStore, StoicRelay) are annotated @available(iOS 26, macOS 26, *)
  • Linking: iOS 15.0+ / macOS 12.0+ — apps with a lower deployment target can depend on the package and gate usage with if #available(iOS 26, macOS 26, *)
  • Swift 6.2
  • Xcode 26+

Acknowledgements

Huge thanks to Gui Rambo and Matt Massicotte for documenting ExtensionKit and writing extensively about it when Apple provided almost no official guidance. Their work made this project possible.

License

MIT. See LICENSE for details.

About

Open-source ExtensionKit journal source package for Stoic

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages