File size: 2,773 Bytes
4fc4790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import AppKit
import Foundation
import OSLog

@MainActor
final class CLIInstallPrompter {
    static let shared = CLIInstallPrompter()
    private let logger = Logger(subsystem: "ai.openclaw", category: "cli.prompt")
    private var isPrompting = false

    func checkAndPromptIfNeeded(reason: String) {
        guard self.shouldPrompt() else { return }
        guard let version = Self.appVersion() else { return }
        self.isPrompting = true
        UserDefaults.standard.set(version, forKey: cliInstallPromptedVersionKey)

        let alert = NSAlert()
        alert.messageText = "Install OpenClaw CLI?"
        alert.informativeText = "Local mode needs the CLI so launchd can run the gateway."
        alert.addButton(withTitle: "Install CLI")
        alert.addButton(withTitle: "Not now")
        alert.addButton(withTitle: "Open Settings")
        let response = alert.runModal()

        switch response {
        case .alertFirstButtonReturn:
            Task { await self.installCLI() }
        case .alertThirdButtonReturn:
            self.openSettings(tab: .general)
        default:
            break
        }

        self.logger.debug("cli install prompt handled reason=\(reason, privacy: .public)")
        self.isPrompting = false
    }

    private func shouldPrompt() -> Bool {
        guard !self.isPrompting else { return false }
        guard AppStateStore.shared.onboardingSeen else { return false }
        guard AppStateStore.shared.connectionMode == .local else { return false }
        guard CLIInstaller.installedLocation() == nil else { return false }
        guard let version = Self.appVersion() else { return false }
        let lastPrompt = UserDefaults.standard.string(forKey: cliInstallPromptedVersionKey)
        return lastPrompt != version
    }

    private func installCLI() async {
        let status = StatusBox()
        await CLIInstaller.install { message in
            await status.set(message)
        }
        if let message = await status.get() {
            let alert = NSAlert()
            alert.messageText = "CLI install finished"
            alert.informativeText = message
            alert.runModal()
        }
    }

    private func openSettings(tab: SettingsTab) {
        SettingsTabRouter.request(tab)
        SettingsWindowOpener.shared.open()
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: .openclawSelectSettingsTab, object: tab)
        }
    }

    private static func appVersion() -> String? {
        Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
    }
}

private actor StatusBox {
    private var value: String?

    func set(_ value: String) {
        self.value = value
    }

    func get() -> String? {
        self.value
    }
}