fix(macos): live-check Pi oauth.json

This commit is contained in:
Peter Steinberger
2025-12-14 04:47:46 +00:00
parent caaa79bb76
commit 26a05292b9
2 changed files with 88 additions and 16 deletions

View File

@@ -119,35 +119,55 @@ enum PiOAuthStore {
}
static func hasAnthropicOAuth() -> Bool {
guard let dict = (try? self.loadStorage()) else { return false }
return dict[self.providerKey] != nil
let url = self.oauthURL()
guard FileManager.default.fileExists(atPath: url.path) else { return false }
guard let data = try? Data(contentsOf: url),
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let storage = json as? [String: Any],
let entry = storage[self.providerKey] as? [String: Any]
else {
return false
}
let refresh = entry["refresh"] as? String
let access = entry["access"] as? String
return (refresh?.isEmpty == false) && (access?.isEmpty == false)
}
static func saveAnthropicOAuth(_ creds: AnthropicOAuthCredentials) throws {
var storage = (try? self.loadStorage()) ?? [:]
storage[self.providerKey] = creds
try self.saveStorage(storage)
}
private static func loadStorage() throws -> [String: AnthropicOAuthCredentials] {
let url = self.oauthURL()
guard FileManager.default.fileExists(atPath: url.path) else { return [:] }
let data = try Data(contentsOf: url)
return try JSONDecoder().decode([String: AnthropicOAuthCredentials].self, from: data)
let existing: [String: Any]
if FileManager.default.fileExists(atPath: url.path),
let data = try? Data(contentsOf: url),
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let dict = json as? [String: Any]
{
existing = dict
} else {
existing = [:]
}
var updated = existing
updated[self.providerKey] = [
"type": creds.type,
"refresh": creds.refresh,
"access": creds.access,
"expires": creds.expires,
]
try self.saveStorage(updated)
}
private static func saveStorage(_ storage: [String: AnthropicOAuthCredentials]) throws {
private static func saveStorage(_ storage: [String: Any]) throws {
let dir = self.oauthDir()
try FileManager.default.createDirectory(
at: dir,
withIntermediateDirectories: true,
attributes: [.posixPermissions: 0o700])
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(storage)
let url = self.oauthURL()
let data = try JSONSerialization.data(withJSONObject: storage, options: [.prettyPrinted, .sortedKeys])
try data.write(to: url, options: [.atomic])
try FileManager.default.setAttributes([.posixPermissions: 0o600], ofItemAtPath: url.path)
}

View File

@@ -57,6 +57,8 @@ struct OnboardingView: View {
@State private var anthropicAuthStatus: String?
@State private var anthropicAuthBusy = false
@State private var anthropicAuthConnected = false
@State private var monitoringAuth = false
@State private var authMonitorTask: Task<Void, Never>?
@State private var identityName: String = ""
@State private var identityTheme: String = ""
@State private var identityEmoji: String = ""
@@ -73,6 +75,7 @@ struct OnboardingView: View {
private let pageWidth: CGFloat = 680
private let contentHeight: CGFloat = 520
private let connectionPageIndex = 1
private let anthropicAuthPageIndex = 2
private let permissionsPageIndex = 5
private var pageOrder: [Int] {
if self.state.connectionMode == .remote {
@@ -149,6 +152,7 @@ struct OnboardingView: View {
.onDisappear {
self.stopPermissionMonitoring()
self.stopDiscovery()
self.stopAuthMonitoring()
}
.task {
await self.refreshPerms()
@@ -324,6 +328,26 @@ struct OnboardingView: View {
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
HStack(spacing: 12) {
Text(PiOAuthStore.oauthURL().path)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
Spacer()
Button("Reveal") {
NSWorkspace.shared.activateFileViewerSelecting([PiOAuthStore.oauthURL()])
}
.buttonStyle(.bordered)
Button("Refresh") {
self.refreshAnthropicOAuthStatus()
}
.buttonStyle(.bordered)
}
Divider().padding(.vertical, 2)
HStack(spacing: 12) {
@@ -1070,6 +1094,7 @@ struct OnboardingView: View {
private func updateMonitoring(for pageIndex: Int) {
self.updatePermissionMonitoring(for: pageIndex)
self.updateDiscoveryMonitoring(for: pageIndex)
self.updateAuthMonitoring(for: pageIndex)
}
private func stopPermissionMonitoring() {
@@ -1084,6 +1109,33 @@ struct OnboardingView: View {
self.masterDiscovery.stop()
}
private func updateAuthMonitoring(for pageIndex: Int) {
let shouldMonitor = pageIndex == self.anthropicAuthPageIndex && self.state.connectionMode == .local
if shouldMonitor, !self.monitoringAuth {
self.monitoringAuth = true
self.startAuthMonitoring()
} else if !shouldMonitor, self.monitoringAuth {
self.stopAuthMonitoring()
}
}
private func startAuthMonitoring() {
self.refreshAnthropicOAuthStatus()
self.authMonitorTask?.cancel()
self.authMonitorTask = Task {
while !Task.isCancelled {
await MainActor.run { self.refreshAnthropicOAuthStatus() }
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
}
}
private func stopAuthMonitoring() {
self.monitoringAuth = false
self.authMonitorTask?.cancel()
self.authMonitorTask = nil
}
private func installCLI() async {
guard !self.installingCLI else { return }
self.installingCLI = true