Client + watchdog menu-bar app: polls the claire daemon (status/fleet/ budget/health), shows NEEDS-YOU, and auto-recovers the daemon on silent DB-write failure via launchctl kickstart. Built on LilithMenuBar / LilithTrayResources. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35 lines
1.3 KiB
Swift
35 lines
1.3 KiB
Swift
import AppKit
|
|
import Foundation
|
|
|
|
/// OS-level control of the `claire web` daemon and dashboard. All entry points
|
|
/// are nonisolated and safe to call from a detached task — they shell out to
|
|
/// `launchctl` / open a URL and don't touch UI state.
|
|
enum DaemonControl {
|
|
/// Restart the daemon: `launchctl kickstart -k gui/<uid>/<label>`.
|
|
/// `-k` kills the running instance first, then relaunches it under launchd.
|
|
/// This is the watchdog's recovery action and the "Restart" menu item.
|
|
static func restartDaemon(label: String) {
|
|
let status = run(["/bin/launchctl", "kickstart", "-k", "gui/\(getuid())/\(label)"])
|
|
NSLog("ClaireTray: kickstart \(label) -> exit \(status)")
|
|
}
|
|
|
|
static func openDashboard(baseURL: String) {
|
|
guard let url = URL(string: baseURL) else { return }
|
|
NSWorkspace.shared.open(url)
|
|
}
|
|
|
|
@discardableResult
|
|
private static func run(_ argv: [String]) -> Int32 {
|
|
let p = Process()
|
|
p.executableURL = URL(fileURLWithPath: argv[0])
|
|
p.arguments = Array(argv.dropFirst())
|
|
do {
|
|
try p.run()
|
|
p.waitUntilExit()
|
|
return p.terminationStatus
|
|
} catch {
|
|
NSLog("ClaireTray: process \(argv) failed: \(error)")
|
|
return -1
|
|
}
|
|
}
|
|
}
|