import AppKit import ClawdbotDiscovery import ClawdbotIPC import Foundation 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() DispatchQueue.main.async { 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() } } }