2026-10-18 22:17:54 -07:00
import AppKit
import ApplicationServices
import ScreenCaptureKit
let orionBundle = "com.kagi.kagimacOS"
2026-10-22 20:20:41 -07:00
let deviceControl = "Device Control and Data Access"
// The server and the app each run byre-hands, and System Settings lists them as two entries: the server's helper by its file name, the app by its own.
let grantee : String = {
var pid = getppid ()
while pid > 1 {
var buf = [ CChar ]( repeating : 0 , count : Int ( MAXPATHLEN ))
if proc_pidpath ( pid , & buf , UInt32 ( buf . count )) > 0 , String ( cString : buf ). hasSuffix ( ".app/Contents/MacOS/Byre" ) {
return "Byre (with byre's app icon)"
}
var info = proc_bsdinfo ()
if proc_pidinfo ( pid , PROC_PIDTBSDINFO , 0 , & info , Int32 ( MemoryLayout < proc_bsdinfo >. size )) <= 0 {
break
}
pid = pid_t ( info . pbi_ppid )
}
return "byre (with the black terminal icon)"
}()
2026-10-18 22:17:54 -07:00
func permission ( _ name : String , _ status : String , _ detail : String , _ pane : String ) -> [ String : String ] {
return [ "name" : name , "status" : status , "detail" : detail , "settings" : "x-apple.systempreferences:com.apple.preference.security?Privacy_ \( pane ) " ]
}
func checkScreenRecording () async -> [ String : String ] {
do {
let content = try await withDisplayAwake { try await SCShareableContent . excludingDesktopWindows ( false , onScreenWindowsOnly : true ) }
return permission ( "Screen Recording" , "granted" , "Listed the screen (displays: \( content . displays . count ) , windows: \( content . windows . count ) )." , "ScreenCapture" )
} catch {
CGRequestScreenCaptureAccess ()
2026-10-22 20:20:41 -07:00
return permission ( "Screen Recording" , "missing" , "Listing the screen failed ( \( error . localizedDescription ) ). Switch \( grantee ) on in Screen Recording." , "ScreenCapture" )
2026-10-18 22:17:54 -07:00
}
}
func checkAccessibility () -> [ String : String ] {
if ! AXIsProcessTrustedWithOptions ([ kAXTrustedCheckOptionPrompt . takeUnretainedValue (): true ] as CFDictionary ) {
2026-10-22 20:20:41 -07:00
return permission ( deviceControl , "missing" , "Switch \( grantee ) on in \( deviceControl ) ." , "Accessibility" )
2026-10-18 22:17:54 -07:00
}
guard let finder = NSRunningApplication . runningApplications ( withBundleIdentifier : "com.apple.finder" ). first else {
2026-10-22 20:20:41 -07:00
return permission ( deviceControl , "unchecked" , "Granted, but Finder isn't running to read its menu bar." , "Accessibility" )
2026-10-18 22:17:54 -07:00
}
do {
guard let bar = try attr ( appElement ( finder . processIdentifier ), kAXMenuBarAttribute ) else {
2026-10-22 20:20:41 -07:00
return permission ( deviceControl , "missing" , "Granted, but reading Finder's menu bar returned nothing. Switch \( grantee ) off and on again in \( deviceControl ) ." , "Accessibility" )
2026-10-18 22:17:54 -07:00
}
2026-10-22 20:20:41 -07:00
return permission ( deviceControl , "granted" , "Read Finder's menu bar (menus: \( try children ( bar as ! AXUIElement ) . count ) )." , "Accessibility" )
2026-10-18 22:17:54 -07:00
} catch let e as ToolError {
2026-10-22 20:20:41 -07:00
return permission ( deviceControl , "missing" , e . message , "Accessibility" )
2026-10-18 22:17:54 -07:00
} catch {
2026-10-22 20:20:41 -07:00
return permission ( deviceControl , "missing" , " \( error ) " , "Accessibility" )
2026-10-18 22:17:54 -07:00
}
}
func checkOrion () -> [ String : String ] {
if NSRunningApplication . runningApplications ( withBundleIdentifier : orionBundle ). isEmpty {
return permission ( "Automation of Orion" , "unchecked" , "Orion isn't running; open it and check again." , "Automation" )
}
let target = NSAppleEventDescriptor ( bundleIdentifier : orionBundle )
let status = AEDeterminePermissionToAutomateTarget ( target . aeDesc , typeWildCard , typeWildCard , true )
if status != noErr {
2026-10-22 20:20:41 -07:00
return permission ( "Automation of Orion" , "missing" , "macOS refused Apple events to Orion ( \( status ) ). Switch Orion on under \( grantee ) in Automation." , "Automation" )
2026-10-18 22:17:54 -07:00
}
do {
let windows = try osa ( "tell application \" Orion \" to count windows" ). int32Value
return permission ( "Automation of Orion" , "granted" , "Orion answered (windows: \( windows ) )." , "Automation" )
} catch let e as ToolError {
return permission ( "Automation of Orion" , "missing" , e . message , "Automation" )
} catch {
return permission ( "Automation of Orion" , "missing" , " \( error ) " , "Automation" )
}
}
2026-10-22 08:01:59 -07:00
func checkFolder ( _ name : String , _ path : String ) -> [ String : String ] {
let dir = ( NSHomeDirectory () as NSString ). appendingPathComponent ( path )
if ! FileManager . default . fileExists ( atPath : dir ) {
return permission ( name , "unchecked" , " \( dir ) doesn't exist." , "FilesAndFolders" )
}
do {
let entries = try FileManager . default . contentsOfDirectory ( atPath : dir )
return permission ( name , "granted" , "Listed \( dir ) (entries: \( entries . count ) )." , "FilesAndFolders" )
} catch {
2026-10-22 20:20:41 -07:00
return permission ( name , "missing" , "Listing \( dir ) failed ( \( error . localizedDescription ) ). Switch \( name ) on under \( grantee ) in Files & Folders." , "FilesAndFolders" )
2026-10-22 08:01:59 -07:00
}
}
2026-10-18 22:17:54 -07:00
func runCheck () {
Task . detached {
2026-10-22 08:01:59 -07:00
let rows = [
await checkScreenRecording (), checkAccessibility (), checkOrion (),
checkFolder ( "Desktop Folder" , "Desktop" ), checkFolder ( "Documents Folder" , "Documents" ),
checkFolder ( "Downloads Folder" , "Downloads" ), checkFolder ( "iCloud Drive" , "Library/Mobile Documents/com~apple~CloudDocs" ),
]
2026-10-18 22:17:54 -07:00
let out = try ! JSONSerialization . data ( withJSONObject : rows , options : . withoutEscapingSlashes )
FileHandle . standardOutput . write ( out + Data ( " \n " . utf8 ))
exit ( 0 )
}
}