import CodexBarCore import Foundation import Testing @testable import CodexBar struct StatusProbeTests { @Test func `parse codex status`() throws { let sample = """ Model: gpt Credits: 980 credits 5h limit: [#####] 75% left Weekly limit: [##] 25% left """ let snap = try CodexStatusProbe.parse(text: sample) #expect(snap.credits == 980) #expect(snap.fiveHourPercentLeft == 75) #expect(snap.weeklyPercentLeft == 25) } @Test func `parse codex status with ansi and resets`() throws { let now = try #require( Calendar(identifier: .gregorian).date(from: DateComponents( timeZone: TimeZone.current, year: 2026, month: 11, day: 26, hour: 8, minute: 0))) let sample = """ \u{001B}[38;5;245mCredits:\u{001B}[0m 557 credits 5h limit: [█████ ] 50% left (resets 09:01) Weekly limit: [███████ ] 85% left (resets 04:01 on 27 Nov) """ let snap = try CodexStatusProbe.parse(text: sample, now: now) #expect(snap.credits == 557) #expect(snap.fiveHourPercentLeft == 50) #expect(snap.weeklyPercentLeft == 85) #expect(snap.fiveHourResetsAt == Calendar(identifier: .gregorian).date(from: DateComponents( timeZone: TimeZone.current, year: 2026, month: 11, day: 26, hour: 9, minute: 1))) #expect(snap.weeklyResetsAt == Calendar(identifier: .gregorian).date(from: DateComponents( timeZone: TimeZone.current, year: 2026, month: 11, day: 27, hour: 4, minute: 1))) } @Test func `parse codex status with weekly only line`() throws { let sample = """ Model: gpt Credits: 980 credits Weekly limit: [##] 25% left """ let snap = try CodexStatusProbe.parse(text: sample) #expect(snap.credits == 980) #expect(snap.fiveHourPercentLeft == nil) #expect(snap.weeklyPercentLeft == 25) } @Test func `parse codex monthly credit limit`() throws { let now = try #require( Calendar(identifier: .gregorian).date(from: DateComponents( timeZone: TimeZone.current, year: 2026, month: 6, day: 23, hour: 12, minute: 0))) let sample = """ Model: codex-status-fixture Monthly credit limit: [██████████████████░░] 92% left (resets 08:00 on 1 Jul) 7,761 of 100,000 credits used """ let snap = try CodexStatusProbe.parse(text: sample, now: now) #expect(snap.codexCreditLimit?.limit == 100_000) #expect(snap.codexCreditLimit?.used == 7761) #expect(snap.codexCreditLimit?.remaining == 92239) #expect(snap.codexCreditLimit?.remainingPercent == 92) #expect(snap.codexCreditLimit?.resetsAt == Calendar(identifier: .gregorian).date(from: DateComponents( timeZone: TimeZone.current, year: 2026, month: 7, day: 1, hour: 8, minute: 0))) } @Test func `parse claude status`() throws { let sample = """ Settings: Status Config Usage (tab to cycle) Current session 1% used (Resets 5am (Europe/Vienna)) Current week (all models) 1% used (Resets Dec 2 at 12am (Europe/Vienna)) Current week (Sonnet only) 1% used (Resets Dec 2 at 12am (Europe/Vienna)) Nov 24, 2025 update: We've increased your limits and removed the Opus cap, so you can use Opus 4.5 up to your overall limit. Sonnet now has its own limit—it's set to match your previous overall limit, so you can use just as much as before. Account: user@example.com Org: Example Org """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 99) #expect(snap.weeklyPercentLeft == 99) #expect(snap.opusPercentLeft == 99) #expect(snap.accountEmail == "user@example.com") #expect(snap.accountOrganization == "Example Org") #expect(snap.primaryResetDescription == "Resets 5am (Europe/Vienna)") #expect(snap.secondaryResetDescription == "Resets Dec 2 at 12am (Europe/Vienna)") #expect(snap.opusResetDescription == "Resets Dec 2 at 12am (Europe/Vienna)") } @Test func `parse claude status with ANSI`() throws { let sample = """ \u{001B}[35mCurrent session\u{001B}[0m 40% used (Resets 11am) Current week (all models) 10% used (Resets Nov 27) Current week (Sonnet only) 0% used (Resets Nov 27) Account: user@example.com Org: ACME \u{001B}[0m """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 60) #expect(snap.weeklyPercentLeft == 90) #expect(snap.opusPercentLeft == 100) #expect(snap.primaryResetDescription == "Resets 11am") #expect(snap.secondaryResetDescription == "Resets Nov 27") #expect(snap.opusResetDescription == "Resets Nov 27") } @Test func `parse claude status legacy opus label`() throws { let sample = """ Current session 12% used (Resets 11am) Current week (all models) 55% used (Resets Nov 21) Current week (Opus) 5% used (Resets Nov 21) Account: user@example.com Org: Example Org """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 88) #expect(snap.weeklyPercentLeft == 45) #expect(snap.opusPercentLeft == 95) #expect(snap.primaryResetDescription == "Resets 11am") #expect(snap.secondaryResetDescription == "Resets Nov 21") #expect(snap.opusResetDescription == "Resets Nov 21") } @Test func `parse claude status remaining keyword`() throws { let sample = """ Current session 12% remaining (Resets 11am) Current week (all models) 40% remaining (Resets Nov 21) """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 12) #expect(snap.weeklyPercentLeft == 40) } @Test func `parse claude status enterprise session only`() throws { let sample = """ Current session █ 2% used Resets 3pm (Europe/Vienna) """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 98) #expect(snap.weeklyPercentLeft == nil) #expect(snap.primaryResetDescription == "Resets 3pm (Europe/Vienna)") #expect(snap.secondaryResetDescription == nil) } @Test func `parse claude status reset mappings with CR line endings`() throws { let sample = "Current session\r" + "██████████████████████████████████████████████████ 17% used\r" + "Resets 12:59pm (Europe/Paris)\r" + "Current week (all models)\r" + "██████████████████████████████████████████████████ 4% used\r" + "Resets Dec 24 at 3:59pm (Europe/Paris)\r" + "Current week (Sonnet only)\r" + "██████████████████████████████████████████████████ 3% used\r" + "Resets Dec 23 at 3:59am (Europe/Paris)\r" let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 83) #expect(snap.weeklyPercentLeft == 96) #expect(snap.opusPercentLeft == 97) #expect(snap.primaryResetDescription == "Resets 12:59pm (Europe/Paris)") #expect(snap.secondaryResetDescription == "Resets Dec 24 at 3:59pm (Europe/Paris)") #expect(snap.opusResetDescription == "Resets Dec 23 at 3:59am (Europe/Paris)") } @Test func `parse claude status reset mappings does not promote weekly reset to session`() throws { let sample = """ Current session ██████████████████████████████████████████████████ 17% used Current week (all models) ██████████████████████████████████████████████████ 4% used Resets Dec 24 at 3:59pm (Europe/Paris) """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 83) #expect(snap.weeklyPercentLeft == 96) #expect(snap.primaryResetDescription == nil) #expect(snap.secondaryResetDescription == "Resets Dec 24 at 3:59pm (Europe/Paris)") } @Test func `parse claude status with plan and ansi noise`() throws { let sample = """ Settings: Status Config Usage Login method: \u{001B}[22mClaude Max Account\u{001B}[0m Account: user@example.com Org: ACME """ // Only care about login/identity; include minimal usage lines to satisfy parser. let text = """ Current session 10% used Current week (all models) 20% used Current week (Opus) 30% used \(sample) """ let snap = try ClaudeStatusProbe.parse(text: text) #expect(snap.loginMethod == "Max") #expect(snap.accountEmail == "user@example.com") #expect(snap.accountOrganization == "ACME") } @Test func `parse claude status with extra usage section`() throws { let sample = """ Settings: Status Config Usage (tab to cycle) Current session ▌ 1% used Resets 3:59pm (Europe/Helsinki) Current week (all models) ▌ 1% used Resets Jan 2, 2026, 10:59pm (Europe/Helsinki) Current week (Sonnet only) 0% used Extra usage Extra usage not enabled • /extra-usage to enable """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 99) #expect(snap.weeklyPercentLeft == 99) #expect(snap.opusPercentLeft == 100) #expect(snap.primaryResetDescription == "Resets 3:59pm (Europe/Helsinki)") #expect(snap.secondaryResetDescription == "Resets Jan 2, 2026, 10:59pm (Europe/Helsinki)") } @Test func `parse claude status ignores status bar context percent`() throws { let sample = """ Claude Code v2.1.29 22:47 | | Opus 4.5 | default | ░░░░░░░░░░ 0% ◯ /ide for Visual Studio Code Settings: Status Config Usage (tab to cycle) Loading usage data… Esc to cancel Curretsession ███████▌15%used Resets 11:30pm (Asia/Calcutta) Current week (all models) █▌ 3% used Resets Feb 12 at 1:30pm (Asia/Calcutta) Current week (Sonnet only) ▌ 1% used Resets Feb 12 at 1:30pm (Asia/Calcutta) """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 85) #expect(snap.weeklyPercentLeft == 97) #expect(snap.opusPercentLeft == 99) } @Test func `parse claude status loading panel surfaces loading stall`() { let sample = """ Claude Code v2.1.29 22:47 | | Opus 4.5 | default | ░░░░░░░░░░ 0% ◯ /ide for Visual Studio Code Settings: Status Config Usage (tab to cycle) Loading usage data… Esc to cancel """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail while /usage is still loading") } catch let ClaudeStatusProbeError.parseFailed(message) { #expect(message.lowercased().contains("loading")) return } catch ClaudeStatusProbeError.timedOut { return } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `parse claude retained usage panel classifies latest loading panel`() { let sample = """ Settings: Status Config Usage (tab to cycle) Current session ███████▌15%used Resets 11:30pm (Asia/Calcutta) Current week (all models) █▌ 3% used Resets Feb 12 at 1:30pm (Asia/Calcutta) Settings: Status Config Usage (tab to cycle) Loading usage data… Esc to cancel """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail while the latest /usage panel is still loading") } catch let ClaudeStatusProbeError.parseFailed(message) { #expect(message.lowercased().contains("loading")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `parse claude status status only output does not fallback to zero`() { let sample = """ Claude Code v2.1.32 01:07 | | Opus 4.6 | default | ░░░░░░░░░░ 0% left Status: Partially Degraded Service /status """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail when /usage windows are missing") } catch ClaudeStatusProbeError.parseFailed { return } catch ClaudeStatusProbeError.timedOut { return } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `parse claude status placeholder usage window does not use status bar percent`() { let sample = """ Claude Code v2.1.32 01:07 | | Opus 4.6 | default | ░░░░░░░░░░ 0% left Settings: Status Config Usage Current session Current week (all models) Current week (Sonnet only) """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail when only status-bar percentages are present") } catch ClaudeStatusProbeError.parseFailed { return } catch ClaudeStatusProbeError.timedOut { return } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `parse claude status compact markers still parse`() throws { let sample = """ Settings:StatusConfigUsage(←/→ortabtocycle) Loadingusagedata… Curretsession ███6%used Resets4:29am(Asia/Calcutta) Currentweek(allmodels) ██4%used ResetsFeb12at1:29pm(Asia/Calcutta) Currentweek(Sonnetonly) ▌1%used ResetsFeb12at1:29pm(Asia/Calcutta) """ let snap = try ClaudeStatusProbe.parse(text: sample) #expect(snap.sessionPercentLeft == 94) #expect(snap.weeklyPercentLeft == 96) #expect(snap.opusPercentLeft == 99) #expect(snap.secondaryResetDescription == "ResetsFeb12at1:29pm(Asia/Calcutta)") #expect(snap.opusResetDescription == "ResetsFeb12at1:29pm(Asia/Calcutta)") } @Test func `parse claude status with bracket plan noise no esc`() throws { let sample = """ Login method: [22m Claude Max Account Account: user@example.com """ let text = """ Current session 10% used Current week (all models) 20% used Current week (Opus) 30% used \(sample) """ let snap = try ClaudeStatusProbe.parse(text: text) #expect(snap.loginMethod == "Max") } @Test func `surfaces claude token expired`() { let sample = """ Settings: Status Config Usage Error: Failed to load usage data: {"type":"error","error":{"type":"authentication_error", "message":"OAuth token has expired. Please obtain a new token or refresh your existing token.", "details":{"error_visibility":"user_facing","error_code":"token_expired"}},\ "request_id":"req_123"} """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail for auth error") } catch let ClaudeStatusProbeError.parseFailed(message) { let lower = message.lowercased() #expect(lower.contains("token")) #expect(lower.contains("login")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `surfaces claude rate limited compact usage error`() { let sample = """ Settings:StatusConfigUsage(←/→ortabtocycle) Error:Failedtoloadusagedata:{"error":{"message":"Ratelimited.Pleasetryagainlater.","type":"rate_limit_error"}} """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail for rate limiting") } catch let ClaudeStatusProbeError.parseFailed(message) { let lower = message.lowercased() #expect(lower.contains("rate")) #expect(lower.contains("limit")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `surfaces claude folder trust prompt`() { let sample = """ Do you trust the files in this folder? /Users/example/project """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail for folder trust prompt") } catch let ClaudeStatusProbeError.parseFailed(message) { #expect(message.lowercased().contains("trust")) #expect(message.contains("/Users/example/project")) #expect(message.contains("cd \"/Users/example/project\" && claude")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `surfaces claude folder trust prompt with CRLF and spaces`() { let sample = "Do you trust the files in this folder?\r\n\r\n/Users/example/My Project\r\n" do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail for folder trust prompt") } catch let ClaudeStatusProbeError.parseFailed(message) { #expect(message.contains("/Users/example/My Project")) #expect(message.contains("cd \"/Users/example/My Project\" && claude")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `surfaces claude folder trust prompt without folder path`() { let sample = """ Do you trust the files in this folder? """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail for folder trust prompt") } catch let ClaudeStatusProbeError.parseFailed(message) { let lower = message.lowercased() #expect(lower.contains("trust")) #expect(lower.contains("auto-accept")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `surfaces claude subscription notice without quota data`() { let sample = """ You are currently using your subscription to power your Claude Code usage """ do { _ = try ClaudeStatusProbe.parse(text: sample) #expect(Bool(false), "Parsing should fail for subscription notice without quota data") } catch let ClaudeStatusProbeError.parseFailed(message) { let lower = message.lowercased() #expect(lower.contains("subscription")) #expect(!lower.contains("still loading")) #expect(ClaudeStatusProbe.isSubscriptionQuotaUnavailableDescription(message)) let errorDescription = ClaudeStatusProbeError.parseFailed(message).localizedDescription #expect(UsageLimitsAvailability.resolve( provider: .claude, snapshot: nil, lastErrorDescription: errorDescription) == .unavailable) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } @Test func `parse claude status subscription notice is distinct from loading stall`() { let subscriptionOnly = "You are currently using your subscription to power your Claude Code usage" let loadingOnly = """ Settings: Status Config Usage (tab to cycle) Loading usage data… Esc to cancel """ do { _ = try ClaudeStatusProbe.parse(text: subscriptionOnly) #expect(Bool(false), "Subscription notice should fail parsing") } catch let ClaudeStatusProbeError.parseFailed(subMessage) { #expect(!subMessage.lowercased().contains("still loading")) } catch { #expect(Bool(false), "Unexpected error for subscription: \(error)") } do { _ = try ClaudeStatusProbe.parse(text: loadingOnly) #expect(Bool(false), "Loading panel should fail parsing") } catch let ClaudeStatusProbeError.parseFailed(loadMessage) { #expect(loadMessage.lowercased().contains("loading")) } catch { #expect(Bool(false), "Unexpected error for loading: \(error)") } } @Test func `parse claude status mixed loading and subscription notice surfaces subscription error`() { // PTY capture containing both an intermediate "Loading usage data…" panel and the final // Claude CLI 2.1.148 subscription notice. The subscription error must be surfaced, not // the still-loading stall, so the UI shows the precise subscription message. let mixedCapture = """ Settings: Status Config Usage (tab to cycle) Loading usage data… Esc to cancel You are currently using your subscription to power your Claude Code usage """ do { _ = try ClaudeStatusProbe.parse(text: mixedCapture) #expect(Bool(false), "Parsing should fail for mixed loading+subscription capture") } catch let ClaudeStatusProbeError.parseFailed(message) { let lower = message.lowercased() #expect(lower.contains("subscription")) #expect(!lower.contains("still loading")) } catch { #expect(Bool(false), "Unexpected error for mixed capture: \(error)") } } @Test func `uses the five hour window to resolve stale claude reset times`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "UTC")) let cases: [(now: DateComponents, text: String, expected: DateComponents)] = [ ( DateComponents(year: 2026, month: 7, day: 9, hour: 15), "Resets 3pm (UTC)", DateComponents(year: 2026, month: 7, day: 9, hour: 15)), ( DateComponents(year: 2026, month: 7, day: 9, hour: 15, minute: 5), "Resets 3pm (UTC)", DateComponents(year: 2026, month: 7, day: 9, hour: 15, minute: 0)), ( DateComponents(year: 2026, month: 7, day: 9, hour: 20), "Resets 3pm (UTC)", DateComponents(year: 2026, month: 7, day: 9, hour: 15)), ( DateComponents(year: 2026, month: 7, day: 9, hour: 23, minute: 59), "Resets 12:01am (UTC)", DateComponents(year: 2026, month: 7, day: 10, hour: 0, minute: 1)), ( DateComponents(year: 2026, month: 7, day: 10, hour: 0, minute: 1), "Resets 11:59pm (UTC)", DateComponents(year: 2026, month: 7, day: 9, hour: 23, minute: 59)), ] for item in cases { let now = try #require(calendar.date(from: item.now)) let parsed = ClaudeStatusProbe.parseResetDate( from: item.text, now: now, expectedWindow: 5 * 60 * 60) #expect(parsed == calendar.date(from: item.expected), "Failed session-window resolution: \(item.text)") } } @Test func `parses claude reset date and time`() throws { let now = Date(timeIntervalSince1970: 1_733_690_000) let parsed = ClaudeStatusProbe.parseResetDate(from: "Resets Dec 9, 8:59am (Europe/Helsinki)", now: now) var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "Europe/Helsinki")) let expected = calendar.date(from: DateComponents( year: calendar.component(.year, from: now), month: 12, day: 9, hour: 8, minute: 59, second: 0)) #expect(parsed == expected) } @Test func `uses the weekly window to resolve stale claude reset dates`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "UTC")) let cases: [(now: DateComponents, text: String, expected: DateComponents)] = [ ( DateComponents(year: 2026, month: 12, day: 31, hour: 23), "Resets Jan 2, 3:15am (UTC)", DateComponents(year: 2027, month: 1, day: 2, hour: 3, minute: 15)), ( DateComponents(year: 2026, month: 12, day: 31, hour: 23), "Resets Jan 2, 3am (UTC)", DateComponents(year: 2027, month: 1, day: 2, hour: 3, minute: 0)), ( DateComponents(year: 2027, month: 1, day: 1, hour: 0, minute: 5), "Resets Dec 31, 11:59pm (UTC)", DateComponents(year: 2026, month: 12, day: 31, hour: 23, minute: 59)), ( DateComponents(year: 2027, month: 1, day: 1, hour: 0, minute: 5), "Resets Dec 31, 11pm (UTC)", DateComponents(year: 2026, month: 12, day: 31, hour: 23, minute: 0)), ( DateComponents(year: 2026, month: 7, day: 9, hour: 15, minute: 5), "Resets Jul 9, 3:00pm (UTC)", DateComponents(year: 2026, month: 7, day: 9, hour: 15, minute: 0)), ] for item in cases { let now = try #require(calendar.date(from: item.now)) let parsed = ClaudeStatusProbe.parseResetDate( from: item.text, now: now, expectedWindow: 7 * 24 * 60 * 60) #expect(parsed == calendar.date(from: item.expected), "Failed weekly-window resolution: \(item.text)") } } @Test func `public claude reset parser remains forward looking`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "UTC")) let cases: [(now: DateComponents, text: String, expected: DateComponents)] = [ ( DateComponents(year: 2026, month: 7, day: 9, hour: 8), "Resets 9pm (UTC)", DateComponents(year: 2026, month: 7, day: 9, hour: 21)), ( DateComponents(year: 2026, month: 7, day: 9, hour: 20), "Resets 1pm (UTC)", DateComponents(year: 2026, month: 7, day: 10, hour: 13)), ( DateComponents(year: 2026, month: 7, day: 9, hour: 12), "Resets Jul 1, 9am (UTC)", DateComponents(year: 2027, month: 7, day: 1, hour: 9)), ] for item in cases { let now = try #require(calendar.date(from: item.now)) let parsed = ClaudeStatusProbe.parseResetDate(from: item.text, now: now) #expect(parsed == calendar.date(from: item.expected), "Failed future resolution: \(item.text)") } } @Test func `stale same day claude reset renders resets now`() throws { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "UTC")) let now = try #require(calendar.date(from: DateComponents( year: 2026, month: 7, day: 9, hour: 15, minute: 5, second: 0))) let resetText = "Resets Jul 9, 3:00pm (UTC)" let resetDate = try #require(ClaudeStatusProbe.parseResetDate( from: resetText, now: now, expectedWindow: 5 * 60 * 60)) let window = RateWindow( usedPercent: 73, windowMinutes: 5 * 60, resetsAt: resetDate, resetDescription: resetText) #expect(UsageFormatter.resetLine(for: window, style: .countdown, now: now) == "Resets now") } @Test func `parses claude reset with dot separated time`() throws { let now = Date(timeIntervalSince1970: 1_733_690_000) let parsed = ClaudeStatusProbe.parseResetDate(from: "Resets Dec 9 at 5.27am (UTC)", now: now) var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "UTC")) let expected = calendar.date(from: DateComponents(year: 2024, month: 12, day: 9, hour: 5, minute: 27)) #expect(parsed == expected) } @Test func `parses claude reset with compact times`() throws { let now = Date(timeIntervalSince1970: 1_733_690_000) let parsedTimeOnly = ClaudeStatusProbe.parseResetDate(from: "Resets 1pm (UTC)", now: now) var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "UTC")) let sameDay = try #require(calendar.date(bySettingHour: 13, minute: 0, second: 0, of: now)) let expected = try #require(calendar.date(byAdding: .day, value: 1, to: sameDay)) #expect(parsedTimeOnly == expected) let parsedDateTime = ClaudeStatusProbe.parseResetDate(from: "Resets Dec 9, 9am", now: now) calendar.timeZone = TimeZone.current let dateExpected = calendar.date(from: DateComponents( year: calendar.component(.year, from: now), month: 12, day: 9, hour: 9, minute: 0, second: 0)) #expect(parsedDateTime == dateExpected) } @Test func `parses claude reset with compact date and time no spaces`() throws { let now = Date(timeIntervalSince1970: 1_773_097_200) // Mar 10, 2026 12:00:00 UTC let parsed = ClaudeStatusProbe.parseResetDate(from: "ResetsMar13at12:30pm(Asia/Calcutta)", now: now) var calendar = Calendar(identifier: .gregorian) calendar.timeZone = try #require(TimeZone(identifier: "Asia/Calcutta")) let expected = calendar.date(from: DateComponents(year: 2026, month: 3, day: 13, hour: 12, minute: 30)) #expect(parsed == expected) } @Test func `live codex status`() async throws { guard ProcessInfo.processInfo.environment["LIVE_CODEX_STATUS"] == "1" else { return } let probe = CodexStatusProbe() do { let snap = try await probe.fetch() let summary = """ Live Codex status: \(snap.rawText) values: 5h \(snap.fiveHourPercentLeft ?? -1)% left, weekly \(snap.weeklyPercentLeft ?? -1)% left, credits \(snap.credits ?? -1) """ print(summary) } catch { // Dump raw PTY text to help debug. let runner = TTYCommandRunner() let res = try runner.run( binary: "codex", send: "/status\n", options: .init(rows: 60, cols: 200, timeout: 12)) print("RAW CODEX PTY OUTPUT BEGIN\n\(res.text)\nRAW CODEX PTY OUTPUT END") let clean = TextParsing.stripANSICodes(res.text) print("CLEAN CODEX OUTPUT BEGIN\n\(clean)\nCLEAN CODEX OUTPUT END") let five = TextParsing.firstInt(pattern: #"5h limit[^\n]*?([0-9]{1,3})%\s+left"#, text: clean) ?? -1 let week = TextParsing.firstInt(pattern: #"Weekly limit[^\n]*?([0-9]{1,3})%\s+left"#, text: clean) ?? -1 let credits = TextParsing.firstNumber(pattern: #"Credits:\s*([0-9][0-9.,]*)"#, text: clean) ?? -1 print("Parsed probes => 5h \(five)% weekly \(week)% credits \(credits)") throw error } } }