import ClawdbotIPC import Foundation extension OnboardingView { @MainActor func refreshPerms() async { await self.permissionMonitor.refreshNow() } @MainActor func request(_ cap: Capability) async { guard !self.isRequesting else { return } self.isRequesting = true defer { isRequesting = false } _ = await PermissionManager.ensure([cap], interactive: true) await self.refreshPerms() } func updatePermissionMonitoring(for pageIndex: Int) { let shouldMonitor = pageIndex == self.permissionsPageIndex if shouldMonitor, !self.monitoringPermissions { self.monitoringPermissions = true PermissionMonitor.shared.register() } else if !shouldMonitor, self.monitoringPermissions { self.monitoringPermissions = false PermissionMonitor.shared.unregister() } } func updateDiscoveryMonitoring(for pageIndex: Int) { let isConnectionPage = pageIndex == self.connectionPageIndex let shouldMonitor = isConnectionPage if shouldMonitor, !self.monitoringDiscovery { self.monitoringDiscovery = true Task { @MainActor in try? await Task.sleep(nanoseconds: 150_000_000) guard self.monitoringDiscovery else { return } self.gatewayDiscovery.start() await self.refreshLocalGatewayProbe() } } else if !shouldMonitor, self.monitoringDiscovery { self.monitoringDiscovery = false self.gatewayDiscovery.stop() } } func updateMonitoring(for pageIndex: Int) { self.updatePermissionMonitoring(for: pageIndex) self.updateDiscoveryMonitoring(for: pageIndex) self.updateAuthMonitoring(for: pageIndex) self.maybeKickoffOnboardingChat(for: pageIndex) } func stopPermissionMonitoring() { guard self.monitoringPermissions else { return } self.monitoringPermissions = false PermissionMonitor.shared.unregister() } func stopDiscovery() { guard self.monitoringDiscovery else { return } self.monitoringDiscovery = false self.gatewayDiscovery.stop() } 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() } } 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) } } } func stopAuthMonitoring() { self.monitoringAuth = false self.authMonitorTask?.cancel() self.authMonitorTask = nil } func installCLI() async { guard !self.installingCLI else { return } self.installingCLI = true defer { installingCLI = false } await CLIInstaller.install { message in self.cliStatus = message } self.refreshCLIStatus() } func refreshCLIStatus() { let installLocation = CLIInstaller.installedLocation() self.cliInstallLocation = installLocation self.cliInstalled = installLocation != nil } func refreshLocalGatewayProbe() async { let port = GatewayEnvironment.gatewayPort() let desc = await PortGuardian.shared.describe(port: port) await MainActor.run { guard let desc else { self.localGatewayProbe = nil return } let command = desc.command.trimmingCharacters(in: .whitespacesAndNewlines) let expectedTokens = ["node", "clawdbot", "tsx", "pnpm", "bun"] let lower = command.lowercased() let expected = expectedTokens.contains { lower.contains($0) } self.localGatewayProbe = LocalGatewayProbe( port: port, pid: desc.pid, command: command, expected: expected) } } func refreshAnthropicOAuthStatus() { _ = ClawdbotOAuthStore.importLegacyAnthropicOAuthIfNeeded() let previous = self.anthropicAuthDetectedStatus let status = ClawdbotOAuthStore.anthropicOAuthStatus() self.anthropicAuthDetectedStatus = status self.anthropicAuthConnected = status.isConnected if previous != status { self.anthropicAuthVerified = false self.anthropicAuthVerificationAttempted = false self.anthropicAuthVerificationFailed = false self.anthropicAuthVerifiedAt = nil } } @MainActor func verifyAnthropicOAuthIfNeeded(force: Bool = false) async { guard self.state.connectionMode == .local else { return } guard self.anthropicAuthDetectedStatus.isConnected else { return } if self.anthropicAuthVerified, !force { return } if self.anthropicAuthVerifying { return } if self.anthropicAuthVerificationAttempted, !force { return } self.anthropicAuthVerificationAttempted = true self.anthropicAuthVerifying = true self.anthropicAuthVerificationFailed = false defer { self.anthropicAuthVerifying = false } guard let refresh = ClawdbotOAuthStore.loadAnthropicOAuthRefreshToken(), !refresh.isEmpty else { self.anthropicAuthStatus = "OAuth verification failed: missing refresh token." self.anthropicAuthVerificationFailed = true return } do { let updated = try await AnthropicOAuth.refresh(refreshToken: refresh) try ClawdbotOAuthStore.saveAnthropicOAuth(updated) self.refreshAnthropicOAuthStatus() self.anthropicAuthVerified = true self.anthropicAuthVerifiedAt = Date() self.anthropicAuthVerificationFailed = false self.anthropicAuthStatus = "OAuth detected and verified." } catch { self.anthropicAuthVerified = false self.anthropicAuthVerifiedAt = nil self.anthropicAuthVerificationFailed = true self.anthropicAuthStatus = "OAuth verification failed: \(error.localizedDescription)" } } }