141 lines
5.1 KiB
Swift
141 lines
5.1 KiB
Swift
import AppKit
|
|
import ClawdbotIPC
|
|
import SwiftUI
|
|
|
|
extension OnboardingView {
|
|
func selectLocalGateway() {
|
|
self.state.connectionMode = .local
|
|
self.preferredGatewayID = nil
|
|
self.showAdvancedConnection = false
|
|
BridgeDiscoveryPreferences.setPreferredStableID(nil)
|
|
}
|
|
|
|
func selectUnconfiguredGateway() {
|
|
Task { await self.onboardingWizard.cancelIfRunning() }
|
|
self.state.connectionMode = .unconfigured
|
|
self.preferredGatewayID = nil
|
|
self.showAdvancedConnection = false
|
|
BridgeDiscoveryPreferences.setPreferredStableID(nil)
|
|
}
|
|
|
|
func selectRemoteGateway(_ gateway: GatewayDiscoveryModel.DiscoveredGateway) {
|
|
Task { await self.onboardingWizard.cancelIfRunning() }
|
|
self.preferredGatewayID = gateway.stableID
|
|
BridgeDiscoveryPreferences.setPreferredStableID(gateway.stableID)
|
|
|
|
if let host = gateway.tailnetDns ?? gateway.lanHost {
|
|
let user = NSUserName()
|
|
self.state.remoteTarget = GatewayDiscoveryModel.buildSSHTarget(
|
|
user: user,
|
|
host: host,
|
|
port: gateway.sshPort)
|
|
ClawdbotConfigFile.setRemoteGatewayUrl(host: host, port: gateway.gatewayPort)
|
|
}
|
|
self.state.remoteCliPath = gateway.cliPath ?? ""
|
|
|
|
self.state.connectionMode = .remote
|
|
MacNodeModeCoordinator.shared.setPreferredBridgeStableID(gateway.stableID)
|
|
}
|
|
|
|
func openSettings(tab: SettingsTab) {
|
|
SettingsTabRouter.request(tab)
|
|
self.openSettings()
|
|
NotificationCenter.default.post(name: .clawdbotSelectSettingsTab, object: tab)
|
|
}
|
|
|
|
func handleBack() {
|
|
withAnimation {
|
|
self.currentPage = max(0, self.currentPage - 1)
|
|
}
|
|
}
|
|
|
|
func handleNext() {
|
|
if self.isWizardBlocking { return }
|
|
if self.currentPage < self.pageCount - 1 {
|
|
withAnimation { self.currentPage += 1 }
|
|
} else {
|
|
self.finish()
|
|
}
|
|
}
|
|
|
|
func finish() {
|
|
UserDefaults.standard.set(true, forKey: "clawdbot.onboardingSeen")
|
|
UserDefaults.standard.set(currentOnboardingVersion, forKey: onboardingVersionKey)
|
|
OnboardingController.shared.close()
|
|
}
|
|
|
|
func copyToPasteboard(_ text: String) {
|
|
let pb = NSPasteboard.general
|
|
pb.clearContents()
|
|
pb.setString(text, forType: .string)
|
|
self.copied = true
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { self.copied = false }
|
|
}
|
|
|
|
func startAnthropicOAuth() {
|
|
guard !self.anthropicAuthBusy else { return }
|
|
self.anthropicAuthBusy = true
|
|
defer { self.anthropicAuthBusy = false }
|
|
|
|
do {
|
|
let pkce = try AnthropicOAuth.generatePKCE()
|
|
self.anthropicAuthPKCE = pkce
|
|
let url = AnthropicOAuth.buildAuthorizeURL(pkce: pkce)
|
|
NSWorkspace.shared.open(url)
|
|
self.anthropicAuthStatus = "Browser opened. After approving, paste the `code#state` value here."
|
|
} catch {
|
|
self.anthropicAuthStatus = "Failed to start OAuth: \(error.localizedDescription)"
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func finishAnthropicOAuth() async {
|
|
guard !self.anthropicAuthBusy else { return }
|
|
guard let pkce = self.anthropicAuthPKCE else { return }
|
|
self.anthropicAuthBusy = true
|
|
defer { self.anthropicAuthBusy = false }
|
|
|
|
guard let parsed = AnthropicOAuthCodeState.parse(from: self.anthropicAuthCode) else {
|
|
self.anthropicAuthStatus = "OAuth failed: missing or invalid code/state."
|
|
return
|
|
}
|
|
|
|
do {
|
|
let creds = try await AnthropicOAuth.exchangeCode(
|
|
code: parsed.code,
|
|
state: parsed.state,
|
|
verifier: pkce.verifier)
|
|
try ClawdbotOAuthStore.saveAnthropicOAuth(creds)
|
|
self.refreshAnthropicOAuthStatus()
|
|
self.anthropicAuthStatus = "Connected. Clawdbot can now use Claude."
|
|
} catch {
|
|
self.anthropicAuthStatus = "OAuth failed: \(error.localizedDescription)"
|
|
}
|
|
}
|
|
|
|
func pollAnthropicClipboardIfNeeded() {
|
|
guard self.currentPage == self.anthropicAuthPageIndex else { return }
|
|
guard self.anthropicAuthPKCE != nil else { return }
|
|
guard !self.anthropicAuthBusy else { return }
|
|
guard self.anthropicAuthAutoDetectClipboard else { return }
|
|
|
|
let pb = NSPasteboard.general
|
|
let changeCount = pb.changeCount
|
|
guard changeCount != self.anthropicAuthLastPasteboardChangeCount else { return }
|
|
self.anthropicAuthLastPasteboardChangeCount = changeCount
|
|
|
|
guard let raw = pb.string(forType: .string), !raw.isEmpty else { return }
|
|
guard let parsed = AnthropicOAuthCodeState.parse(from: raw) else { return }
|
|
guard let pkce = self.anthropicAuthPKCE, parsed.state == pkce.verifier else { return }
|
|
|
|
let next = "\(parsed.code)#\(parsed.state)"
|
|
if self.anthropicAuthCode != next {
|
|
self.anthropicAuthCode = next
|
|
self.anthropicAuthStatus = "Detected `code#state` from clipboard."
|
|
}
|
|
|
|
guard self.anthropicAuthAutoConnectClipboard else { return }
|
|
Task { await self.finishAnthropicOAuth() }
|
|
}
|
|
}
|