Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public struct ClaudeSwapUsageWindow: Equatable, Sendable {
public enum ClaudeSwapUsageStatus: Equatable, Sendable {
case ok
case tokenExpired
case reloginRequired
case apiKey
case keychainUnavailable
case noCredentials
Expand All @@ -85,6 +86,7 @@ public enum ClaudeSwapUsageStatus: Equatable, Sendable {
switch rawValue {
case "ok": self = .ok
case "token_expired": self = .tokenExpired
case "relogin_required": self = .reloginRequired
case "api_key": self = .apiKey
case "keychain_unavailable": self = .keychainUnavailable
case "no_credentials": self = .noCredentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public enum ClaudeSwapAccountProjection {
: nil
case .tokenExpired:
"Token expired. Switch to this account in claude-swap to refresh it."
case .reloginRequired:
"Re-login required. Re-authenticate this account in claude-swap."
case .apiKey:
"API-key account; subscription usage is unavailable."
case .keychainUnavailable:
Expand All @@ -117,7 +119,7 @@ public enum ClaudeSwapAccountProjection {
switch row.usageStatus {
case .ok, .apiKey, .unavailable:
true
case .tokenExpired, .keychainUnavailable, .noCredentials, .unknown:
case .tokenExpired, .reloginRequired, .keychainUnavailable, .noCredentials, .unknown:
false
}
}
Expand Down
2 changes: 2 additions & 0 deletions Tests/CodexBarTests/CLICardsClaudeSwapTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ struct CLICardsClaudeSwapTests {
let statuses: [ClaudeSwapUsageStatus] = [
.apiKey,
.tokenExpired,
.reloginRequired,
.keychainUnavailable,
.noCredentials,
.unavailable,
Expand All @@ -265,6 +266,7 @@ struct CLICardsClaudeSwapTests {
#expect(output.cards.map(\.accountProblem) == [
"API-key account; subscription usage is unavailable.",
"Token expired. Switch to this account in claude-swap to refresh it.",
"Re-login required. Re-authenticate this account in claude-swap.",
"claude-swap could not read the active account's Keychain entry.",
"No stored credentials for this account slot.",
"Usage fetch failed.",
Expand Down
1 change: 1 addition & 0 deletions Tests/CodexBarTests/ClaudeSwapAccountProjectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ struct ClaudeSwapAccountProjectionTests {
func `maps sentinel statuses to per account errors without usage`() throws {
let rows: [(ClaudeSwapUsageStatus, String)] = [
(.tokenExpired, "Token expired"),
(.reloginRequired, "Re-login required"),
(.apiKey, "API-key account"),
(.keychainUnavailable, "Keychain"),
(.noCredentials, "No stored credentials"),
Expand Down
24 changes: 24 additions & 0 deletions Tests/CodexBarTests/ClaudeSwapListParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,30 @@ struct ClaudeSwapListParserTests {
])
}

@Test
func `projects relogin required status to recovery guidance`() throws {
let json = """
{
"schemaVersion": 1,
"activeAccountNumber": null,
"accounts": [
{
"number": 1,
"email": "expired@example.com",
"active": false,
"usageStatus": "relogin_required",
"usage": null
}
]
}
"""

let list = try self.parse(json)
let account = try #require(ClaudeSwapAccountProjection.accountSnapshots(from: list).first)
#expect(account.error == "Re-login required. Re-authenticate this account in claude-swap.")
#expect(account.canActivate == false)
}

@Test
func `surfaces schema v1 error envelope`() throws {
let json = """
Expand Down