Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jasonkneen/openclicky/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through embedding OpenClicky’s full AI runtime into your own macOS SwiftUI app. By the end you will have a working OpenClickySDKSession presenting the conversation panel as a sheet, with voice, agent mode, and TTS fully operational.
OpenClicky is a source-level integration — you add its Swift files and resources directly to your host app target. There is no Swift Package Manager product yet.

1

Add source files to your host target

In Xcode, add the Swift sources from the OpenClicky repository to your host app target.Required files:
  • cursor-buddy/OpenClickySDK.swift — the public SDK entry point
  • cursor-buddy/CompanionManager.swift — the central runtime state manager
  • cursor-buddy/CompanionPanelView.swift — the SwiftUI panel view
  • All other .swift files in cursor-buddy/ that are depended on by the above (including Codex/agent managers, model managers, overlay managers, settings managers, and dictation managers)
The easiest approach is to include the entire cursor-buddy/*.swift folder and exclude only:
  • cursor-buddy/cursor_buddyApp.swift — this is the menu-bar app’s @main entry point and will conflict with your host app’s entry point
cursor-buddy/
├── OpenClickySDK.swift          ← required
├── CompanionManager.swift       ← required
├── CompanionPanelView.swift     ← required
├── cursor_buddyApp.swift        ← EXCLUDE THIS
└── *.swift                      ← include all others
In Xcode, select all files in cursor-buddy/, deselect cursor_buddyApp.swift, and add them to your target’s “Compile Sources” build phase.
2

Bundle resources into your host app

OpenClicky loads several resource files from Bundle.main at runtime. You must copy these into your host app bundle at the resource root (not inside a subdirectory).Required resources:
ResourcePurpose
SOUL.mdOpenClicky’s persona and behavioral instructions
OpenClickyModelInstructions.mdModel system prompt supplement
AGENTS.mdAgent mode configuration
OpenClickyBundledSkills/Built-in skill definitions
OpenClickyBundledWikiSeed/Bundled knowledge base seed
CodexRuntime/Codex agent runtime files (required for Agent Mode)
ClaudeAgentSDKBridge/Claude Agent SDK bridge (required for local Claude auth)
agent-done.mp3Completion chime audio
If you are not using Agent Mode, you can still start the runtime and test text prompts — but CodexRuntime and related assets are required for Codex-backed agent workflows.
Add a Run Script build phase to your target that copies these resources into the built bundle:
ROOT="${PROJECT_DIR}/AppResources/OpenClicky"
DST="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
mkdir -p "$DST"
for rel in SOUL.md OpenClickyModelInstructions.md AGENTS.md OpenClickyBundledSkills OpenClickyBundledWikiSeed CodexRuntime ClaudeAgentSDKBridge agent-done.mp3; do
  if [ -e "$ROOT/$rel" ]; then
    rm -rf "$DST/$rel"
    /usr/bin/ditto "$ROOT/$rel" "$DST/$rel"
  fi
done
Adjust ROOT to wherever you store the OpenClicky resources relative to your project.
3

Add privacy keys to Info.plist

OpenClicky requires several macOS privacy usage descriptions. Add these keys to your host app’s Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>OpenClicky uses the microphone for voice input.</string>

<key>NSSpeechRecognitionUsageDescription</key>
<string>OpenClicky uses speech recognition to transcribe voice prompts.</string>

<key>NSScreenCaptureUsageDescription</key>
<string>OpenClicky captures your screen to provide visual context to AI responses.</string>

<key>NSAppleEventsUsageDescription</key>
<string>OpenClicky uses Apple Events for local automation commands.</string>
You should also ensure your app’s entitlements and capabilities allow access to:
  • com.apple.security.network.client — for API requests to Claude, ElevenLabs, Cartesia, and Deepgram
  • com.apple.security.device.microphone — for voice capture
  • com.apple.security.device.audio-input — for AVFoundation microphone access
  • ScreenCaptureKit access via the macOS privacy prompt (triggered at runtime by OpenClicky)
4

Launch and embed in SwiftUI

The preferred pattern uses @StateObject to own the session at your view or scene level, starts the runtime on .onAppear, and presents the panel as a .sheet.Minimal host view:
import SwiftUI

struct HostView: View {
    @StateObject private var sdk = OpenClickySDKSession(mode: .embeddedWindow)
    @State private var showOpenClicky = false

    var body: some View {
        VStack {
            Button("Open OpenClicky") { showOpenClicky = true }
        }
        .onAppear {
            sdk.start()
        }
        .sheet(isPresented: $showOpenClicky) {
            sdk.makePanelView(
                isPanelPinned: false,
                actions: .init(
                    onPanelDismiss: { showOpenClicky = false },
                    onQuit:         { showOpenClicky = false },
                    onOpenHUD:      { /* route to your HUD */ },
                    onOpenMemory:   { /* route to your memory UI */ },
                    onOpenFeedback: { /* open issue page or your feedback form */ },
                    onShowSettings: { /* route to your settings UI */ }
                ),
                setPanelPinned: { _ in }
            )
            .frame(minWidth: 356, minHeight: 720)
            .padding(8)
        }
        .onDisappear {
            sdk.stop()
        }
    }
}
Recommended host runtime wrapper (OpenClickyHostRuntime.swift):For larger apps, extract the SDK session into its own ObservableObject and inject it via .environmentObject:
import Foundation
import SwiftUI

@MainActor
final class OpenClickyHostRuntime: ObservableObject {
    let sdk = OpenClickySDKSession(mode: .embeddedWindow)

    @Published var isPanelPresented = false
    @Published var isPanelPinned = false

    func start() {
        sdk.start()
    }

    func stop() {
        sdk.stop()
    }

    func makePanel(onDismiss: @escaping () -> Void) -> some View {
        sdk.makePanelView(
            isPanelPinned: isPanelPinned,
            actions: .init(
                onPanelDismiss: onDismiss,
                onQuit: onDismiss,
                onOpenHUD: {
                    // Route to your HUD/window
                },
                onOpenMemory: {
                    // Route to your memory UI
                },
                onOpenFeedback: {
                    // Open your preferred feedback UX
                },
                onShowSettings: {
                    // Route to your settings UI
                }
            ),
            setPanelPinned: { pinned in
                isPanelPinned = pinned
            }
        )
    }
}
App scene and ContentView:
import SwiftUI

@main
struct MyHostApp: App {
    @StateObject private var openClickyRuntime = OpenClickyHostRuntime()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(openClickyRuntime)
                .onAppear {
                    openClickyRuntime.start()
                }
                .onDisappear {
                    openClickyRuntime.stop()
                }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject private var openClickyRuntime: OpenClickyHostRuntime

    var body: some View {
        VStack(spacing: 16) {
            Button("Open OpenClicky") {
                openClickyRuntime.isPanelPresented = true
            }
        }
        .sheet(isPresented: $openClickyRuntime.isPanelPresented) {
            openClickyRuntime.makePanel {
                openClickyRuntime.isPanelPresented = false
            }
            .frame(minWidth: 356, minHeight: 720)
        }
    }
}
Using AppKit (NSWindow) instead of .sheet?
  • Keep one persistent OpenClickySDKSession at a controller level.
  • Create an NSHostingView(rootView:) from runtime.makePanel(...).
  • Present and close that hosting view in your normal host window flow.
  • Still call OpenClickySDKSession.start() once at app launch and stop() on app shutdown.
Gate sdk.start() to avoid starting before your UI is mounted and macOS privacy permissions have been requested. Starting too early can cause permission prompts to appear before the user has context.
5

Wire API keys

Once the runtime is started, set your API keys. OpenClicky persists these through the same code paths as the native app:
sdk.setAnthropicAPIKey("sk-ant-...")
sdk.setCodexAgentAPIKey("...")     // OpenAI-compatible key for Codex/agent mode
sdk.setElevenLabsAPIKey("...")     // For ElevenLabs TTS voice
sdk.setCartesiaAPIKey("...")       // For Cartesia TTS voice
These setters update existing OpenClicky code paths and persist the keys the same way the native app does. You do not need to restart the session after setting keys.Sending prompts programmatically:
// Standard text prompt through the voice-response path
sdk.submitTextPrompt("Summarize this page")

// Route directly into Agent Mode
sdk.submitAgentPrompt("Create a bug report from these logs")

// Trigger voice capture (for custom PTT buttons)
sdk.startVoiceCapture()  // begin follow-up dictation
sdk.stopVoiceCapture()   // end follow-up dictation early
Checking runtime state:
// isStarted is @Published — observe it in SwiftUI or Combine
if sdk.isStarted {
    print("OpenClicky runtime is running")
}
Logging:OpenClicky writes structured request logs to:
~/Library/Application Support/OpenClicky/Logs/messages-*.jsonl
Filter on openclicky.request.completed to validate completions and cancellations during development.

Known Limitations

  • This is a source-level SDK path, not yet a dedicated SPM product.
  • Your host app may receive permission re-prompts when features like microphone or screen capture are first used.
  • In embedded mode, global push-to-talk, overlay auto-show, and onboarding tied to menu-bar state are all disabled by design.
  • The CodexRuntime and ClaudeAgentSDKBridge resources are required for Agent Mode and local Claude auth respectively. Text-only voice responses work without them if an Anthropic API key is configured.

Build docs developers (and LLMs) love