import Foundation public enum OpenAIDashboardParser { /// Extracts the signed-in email from the embedded `client-bootstrap` JSON payload, if present. /// /// The Codex usage dashboard currently ships a JSON blob in: /// ``. /// WebKit `document.body.innerText` often does not include the email, so we parse it from HTML. public static func parseSignedInEmailFromClientBootstrap(html: String) -> String? { guard let data = self.clientBootstrapJSONData(fromHTML: html) else { return nil } guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil } // Fast path: common structure. if let dict = json as? [String: Any] { if let session = dict["session"] as? [String: Any], let user = session["user"] as? [String: Any], let email = user["email"] as? String, email.contains("@") { return email.trimmingCharacters(in: .whitespacesAndNewlines) } if let user = dict["user"] as? [String: Any], let email = user["email"] as? String, email.contains("@") { return email.trimmingCharacters(in: .whitespacesAndNewlines) } } // Fallback: BFS scan for an email key/value. var queue: [Any] = [json] var seen = 0 while !queue.isEmpty, seen < 4000 { let cur = queue.removeFirst() seen += 1 if let dict = cur as? [String: Any] { for (k, v) in dict { if k.lowercased() == "email", let email = v as? String, email.contains("@") { return email.trimmingCharacters(in: .whitespacesAndNewlines) } queue.append(v) } } else if let arr = cur as? [Any] { queue.append(contentsOf: arr) } } return nil } /// Extracts the auth status from `client-bootstrap`, if present. /// Expected values include `logged_in` and `logged_out`. public static func parseAuthStatusFromClientBootstrap(html: String) -> String? { guard let data = self.clientBootstrapJSONData(fromHTML: html) else { return nil } guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil } guard let dict = json as? [String: Any] else { return nil } if let authStatus = dict["authStatus"] as? String, !authStatus.isEmpty { return authStatus.trimmingCharacters(in: .whitespacesAndNewlines) } return nil } public static func parseCodeReviewRemainingPercent(bodyText: String) -> Double? { let cleaned = bodyText.replacingOccurrences(of: "\r", with: "\n") let patterns = [ #"Code\s*review[^0-9%]*([0-9]{1,3})%\s*remaining"#, #"Core\s*review[^0-9%]*([0-9]{1,3})%\s*remaining"#, ] for pattern in patterns { guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { continue } let range = NSRange(cleaned.startIndex..= 2, let r = Range(match.range(at: 1), in: cleaned) else { continue } if let val = Double(cleaned[r]) { return min(100, max(0, val)) } } return nil } public static func parseCreditEvents(rows: [[String]]) -> [CreditEvent] { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "MMM d, yyyy" return rows.compactMap { row in guard row.count >= 3 else { return nil } let dateString = row[0] let service = row[1].trimmingCharacters(in: .whitespacesAndNewlines) let amountString = row[2] guard let date = formatter.date(from: dateString) else { return nil } let creditsUsed = Self.parseCreditsUsed(amountString) return CreditEvent(date: date, service: service, creditsUsed: creditsUsed) } .sorted { $0.date > $1.date } } private static func parseCreditsUsed(_ text: String) -> Double { let cleaned = text .replacingOccurrences(of: ",", with: "") .replacingOccurrences(of: "credits", with: "", options: .caseInsensitive) .trimmingCharacters(in: .whitespacesAndNewlines) return Double(cleaned) ?? 0 } // MARK: - Private private static func clientBootstrapJSONData(fromHTML html: String) -> Data? { let needle = "id=\"client-bootstrap\"" guard let idRange = html.range(of: needle) else { return nil } // Find the end of the opening ", range: contentStart..