Import Coucou 0.1.9 (Louis-CFM/coucou@83708fe), Mac app only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
|
||||
// A question Claude Code asks (AskUserQuestion), as the iPhone gets it: the
|
||||
// questions with their choices. The Mac writes it, encrypted, in the session
|
||||
// record; the iPhone answers with an `Answer` record carrying the same
|
||||
// fingerprint and the labels picked. The Mac only accepts labels that are
|
||||
// among the choices of the question still waiting.
|
||||
|
||||
struct QuestionPayload: Codable, Equatable, Sendable {
|
||||
struct Option: Codable, Equatable, Sendable {
|
||||
var label: String
|
||||
var description: String
|
||||
}
|
||||
|
||||
struct Item: Codable, Equatable, Sendable {
|
||||
var question: String
|
||||
var header: String
|
||||
var options: [Option]
|
||||
var multiSelect: Bool
|
||||
}
|
||||
|
||||
var items: [Item]
|
||||
|
||||
/// Stable identifier of this exact question.
|
||||
var fingerprint: String {
|
||||
let raw = items.map { item in
|
||||
([item.question, item.multiSelect ? "multi" : "single"] + item.options.map(\.label)).joined(separator: "\u{1F}")
|
||||
}.joined(separator: "\u{1E}")
|
||||
return SHA256.hash(data: Data(raw.utf8)).map { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
var json: String {
|
||||
guard let data = try? JSONEncoder().encode(self) else { return "" }
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
static func decode(_ json: String) -> QuestionPayload? {
|
||||
guard !json.isEmpty else { return nil }
|
||||
return try? JSONDecoder().decode(QuestionPayload.self, from: Data(json.utf8))
|
||||
}
|
||||
|
||||
/// The picks sent back: one list of labels per question.
|
||||
static func encodeSelections(_ selections: [[String]]) -> String {
|
||||
guard let data = try? JSONEncoder().encode(selections) else { return "[]" }
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
static func decodeSelections(_ json: String) -> [[String]]? {
|
||||
try? JSONDecoder().decode([[String]].self, from: Data(json.utf8))
|
||||
}
|
||||
|
||||
/// True when every pick is one of that question's choices, with one pick
|
||||
/// per single-choice question.
|
||||
func accepts(_ selections: [[String]]) -> Bool {
|
||||
guard selections.count == items.count else { return false }
|
||||
for (item, picks) in zip(items, selections) {
|
||||
let labels = Set(item.options.map(\.label))
|
||||
guard !picks.isEmpty, picks.allSatisfy(labels.contains) else { return false }
|
||||
if !item.multiSelect && picks.count != 1 { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user