mac: register the login item with SMAppService

The menubar told System Events to make its login item, so macOS asked for
Automation access on first launch. SMAppService.mainApp does it in-process
with no Automation grant. No AppleScript fallback: a failure must not bring
the prompt back. Package floor is macOS 14, so the 13+ API needs no
availability guard.

Fixes #1026
This commit is contained in:
iamtoruk 2026-08-18 07:48:03 -07:00
parent 9905e8f8d0
commit 185d6b3b31
2 changed files with 9 additions and 22 deletions

View file

@ -23,6 +23,7 @@
- **One rule for every cache file.** `CODEBURN_CACHE_DIR` when set, otherwise `~/.cache/codeburn`. `XDG_CACHE_HOME` is no longer consulted; the sync ledger, the only file that ever honored it, is merged into the canonical location on first read and the legacy copy is retired, so nothing is re-uploaded after the move. (#972)
### Fixed (Desktop & Menubar)
- **First launch no longer asks to control System Events.** The macOS menubar registered its login item by driving System Events over AppleScript, which made macOS put up an Automation consent dialog the first time the app ran. It now registers itself through `SMAppService.mainApp`, an in-process call that needs no Automation grant; there is no AppleScript fallback, so a failure logs and leaves the login item unset rather than bringing the prompt back. The same `codeburn.loginItemRegistered` guard still limits this to the first launch, so a login item you removed by hand stays removed. (#1026)
- **The resident `codeburn serve` child.** The first real panel request is also the cache warm-up, so startup never runs an artificial warm-up query beside a duplicate one-shot child; each served command carries its own read-only option allowlist, and anything outside it falls back to a normal spawn; the child exits when its stdin closes, so it can never outlive the app. Requests whose response exceeds the 16 MiB frame limit still replace the child, but that deliberate kill no longer spends the resident's unexpected-death budget. (#972)
### Fixed

View file

@ -2,6 +2,7 @@ import Foundation
import SwiftUI
import AppKit
import Observation
import ServiceManagement
private let refreshIntervalSeconds: UInt64 = 30
private let forceRefreshWatchdogSeconds: TimeInterval = 90
@ -281,34 +282,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate, NSM
let key = "codeburn.loginItemRegistered"
guard !UserDefaults.standard.bool(forKey: key) else { return }
let appPath = Bundle.main.bundlePath
let script = "tell application \"System Events\" to make login item at end with properties {path:\(appleScriptStringLiteral(appPath)), hidden:false}"
let process = Process()
process.launchPath = "/usr/bin/osascript"
process.arguments = ["-e", script]
process.standardOutput = FileHandle.nullDevice
process.standardError = FileHandle.nullDevice
// Registers in-process. The old path told System Events to make the login
// item, which made macOS ask for Automation access on first launch (#1026).
// No AppleScript fallback: a failure here must not bring that prompt back.
do {
try process.run()
process.waitUntilExit()
if process.terminationStatus == 0 {
UserDefaults.standard.set(true, forKey: key)
if SMAppService.mainApp.status != .enabled {
try SMAppService.mainApp.register()
}
UserDefaults.standard.set(true, forKey: key)
} catch {
NSLog("CodeBurn: Login item registration failed: \(error)")
NSLog("CodeBurn: login item registration failed: \(error.localizedDescription)")
}
}
private func appleScriptStringLiteral(_ value: String) -> String {
var escaped = value.replacingOccurrences(of: "\\", with: "\\\\")
escaped = escaped.replacingOccurrences(of: "\"", with: "\\\"")
escaped = escaped.replacingOccurrences(of: "\r", with: "")
escaped = escaped.replacingOccurrences(of: "\n", with: "")
return "\"\(escaped)\""
}
private var lastRefreshTime: Date = .distantPast
/// Anchors the shallow provider-root snapshot only after a complete usage
/// refresh succeeds. It sits beside the cadence anchor so a failed fetch