AppRuntimeKit is a Swift package for building launcher apps, menu bar utilities, automation tools, companion apps, and duplicate-process activation requests with a clean split between provider-neutral runtime values and live AppKit integration.
The package provides two products:
AppRuntimeKitfor provider-neutral process snapshots, runtime events, and runtime request result values.AppRuntimeKitAppKitforNSWorkspace,NSRunningApplication, URL/file/application opening, and distributed-notification backed runtime operations.
- launcher applications
- menu bar utilities
- automation tools
- companion applications
- duplicate-process activation requests
- runtime-aware diagnostics and support tools
Store immutable process identifiers, bundle identifiers, display names, and termination state without keeping a live process handle.
Represent launch, termination, activation, and deactivation notifications with provider-neutral snapshot values.
Use ApplicationRequestResult for compact request outcomes, specialized with
ApplicationOpeningError or ApplicationRuntimeRequestError for validated error cases.
Use ApplicationTerminationMode to choose graceful or forced termination requests.
Query, open, activate, terminate, observe, and wait for macOS applications through
the AppRuntimeKitAppKit product.
Coordinate activation requests from duplicate processes with distributed notifications.
Asynchronous open methods require an explicit NSWorkspace.OpenConfiguration so
call sites do not become ambiguous with the synchronous open methods.
NSWorkspace and NSRunningApplication backed APIs live in AppRuntimeKitAppKit
and are main-actor isolated.
AppRuntimeKit owns reusable process snapshots, lifecycle events, typed request outcomes, and validation errors. AppRuntimeKitAppKit owns the AppKit-backed runtime adapter and duplicate-process activation request coordination.
Host apps remain responsible for product-specific lifecycle policy, URL/file/application exposure policy, UI, persistence, permissions onboarding, analytics, telemetry, privacy disclosures, and release packaging.
Inputs are validated before use, and result types distinguish invalid input, missing applications, stale process identity, and rejected runtime requests. Process-targeted activation and termination overloads can validate an expected bundle identifier before sending a request. See the API documentation for detailed behavior.
- macOS 14 or later
- Swift 6.0 or later
Add this package to your Swift Package dependencies:
.package(url: "https://github.com/naviapps/app-runtime-kit.git", from: "0.2.0")Then add the product that matches the boundary you need:
.product(name: "AppRuntimeKit", package: "app-runtime-kit"),
.product(name: "AppRuntimeKitAppKit", package: "app-runtime-kit")Open an application only when it is not already running:
import AppRuntimeKit
import AppRuntimeKitAppKit
@MainActor
func openSafariIfNeeded() -> ApplicationRequestResult<ApplicationOpeningError>? {
let runtime = ApplicationRuntime()
guard runtime.runningApplications(bundleIdentifier: "com.apple.Safari").isEmpty else {
return nil
}
return runtime.openApplication(bundleIdentifier: "com.apple.Safari")
}Use ApplicationProcessSnapshot for explicit process snapshot values:
the explicit initializer records the values you pass and leaves live-process
validation to runtime adapters.
import AppKit
import AppRuntimeKit
import AppRuntimeKitAppKit
let snapshot = ApplicationProcessSnapshot(
processIdentifier: 123,
bundleIdentifier: "com.example.app",
displayName: "Example"
)
@MainActor
func currentApplicationSnapshot() -> ApplicationProcessSnapshot {
ApplicationProcessSnapshot(runningApplication: NSRunningApplication.current)
}Use ApplicationRuntime for live macOS runtime operations:
import AppRuntimeKit
import AppRuntimeKitAppKit
@MainActor
func runningExampleApplications() -> [ApplicationProcessSnapshot] {
let runtime = ApplicationRuntime()
return runtime.runningApplications(bundleIdentifier: "com.example.app")
}
@MainActor
func openExampleApplication() -> ApplicationRequestResult<ApplicationOpeningError> {
let runtime = ApplicationRuntime()
return runtime.openApplication(bundleIdentifier: "com.example.app")
}Keep event observation tasks alive while the app should receive events. Cancel the task when the observation is no longer needed.
import AppRuntimeKit
import AppRuntimeKitAppKit
@MainActor
final class RuntimeEventObserver {
private let runtime = ApplicationRuntime()
private let handleEvent: @MainActor (ApplicationRuntimeEvent) -> Void
private var task: Task<Void, Never>?
init(handleEvent: @escaping @MainActor (ApplicationRuntimeEvent) -> Void) {
self.handleEvent = handleEvent
}
func start() {
stop()
let runtime = runtime
let handleEvent = handleEvent
task = Task { @MainActor in
for await event in runtime.events() {
handleEvent(event)
}
}
}
func stop() {
task?.cancel()
task = nil
}
deinit {
task?.cancel()
}
}Keep ApplicationDuplicateActivationCoordinator alive while the app should receive
activation requests from duplicate processes. Calling stop() or releasing the
coordinator removes the observer.
import AppRuntimeKitAppKit
@MainActor
final class DuplicateActivationCoordinatorStore {
private var coordinator: ApplicationDuplicateActivationCoordinator?
func start() throws -> ApplicationDuplicateActivationCoordinator.CoordinationResult {
stop()
let coordinator = try ApplicationDuplicateActivationCoordinator(
bundleIdentifier: "com.example.app"
)
let result = coordinator.coordinateActivatingCurrentApplication()
if result == .unique {
self.coordinator = coordinator
}
return result
}
func stop() {
coordinator?.stop()
coordinator = nil
}
}Run the package check with:
make checkGitHub Actions runs the same check on pull requests and pushes to main.
See CONTRIBUTING.md. Release notes are in CHANGELOG.md.
Report vulnerabilities privately. See SECURITY.md.
AppRuntimeKit is released under the MIT License. See LICENSE.