diff --git a/Sources/PokeTokenBar/Core/CodexRateLimitsProvider.swift b/Sources/PokeTokenBar/Core/CodexRateLimitsProvider.swift index 871cb0901..89b81a208 100644 --- a/Sources/PokeTokenBar/Core/CodexRateLimitsProvider.swift +++ b/Sources/PokeTokenBar/Core/CodexRateLimitsProvider.swift @@ -24,6 +24,7 @@ struct CodexRateLimitsProvider: CodexLimitsProviding { // ChatGPT.app bundles the codex CLI; users on the desktop app often have // no standalone install, so this is the only resolvable path for them. // Listed last so dedicated installs win over the bundled copy. + "/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex", "/Applications/ChatGPT.app/Contents/Resources/codex", ] } diff --git a/Tests/PokeTokenBarTests/CodexRateLimitsProviderTests.swift b/Tests/PokeTokenBarTests/CodexRateLimitsProviderTests.swift new file mode 100644 index 000000000..7bc765102 --- /dev/null +++ b/Tests/PokeTokenBarTests/CodexRateLimitsProviderTests.swift @@ -0,0 +1,31 @@ +import XCTest +@testable import PokeTokenBar + +final class CodexRateLimitsProviderTests: XCTestCase { + private let nestedChatGPTBinary = + "/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex" + private let legacyChatGPTBinary = "/Applications/ChatGPT.app/Contents/Resources/codex" + + func testDefaultCandidatesSupportCurrentAndLegacyChatGPTBundles() { + // Use the production defaults: injected fake CLI paths missed this packaging change. + let candidates = CodexRateLimitsProvider().binaryCandidates + XCTAssertTrue(candidates.contains(nestedChatGPTBinary)) + XCTAssertTrue(candidates.contains(legacyChatGPTBinary)) + } + + func testDedicatedInstallsPrecedeBothChatGPTBundleLayouts() throws { + let candidates = CodexRateLimitsProvider().binaryCandidates + for bundledBinary in [nestedChatGPTBinary, legacyChatGPTBinary] { + let bundledIndex = try XCTUnwrap(candidates.firstIndex(of: bundledBinary)) + for dedicatedBinary in [ + "/Applications/Codex.app/Contents/Resources/codex", + "\(FileManager.default.homeDirectoryForCurrentUser.path)/.codex/bin/codex", + "/opt/homebrew/bin/codex", + "/usr/local/bin/codex", + ] { + let dedicatedIndex = try XCTUnwrap(candidates.firstIndex(of: dedicatedBinary)) + XCTAssertLessThan(dedicatedIndex, bundledIndex) + } + } + } +} diff --git a/docs/reference/defect-log.md b/docs/reference/defect-log.md index b09bf6596..33d435bed 100644 --- a/docs/reference/defect-log.md +++ b/docs/reference/defect-log.md @@ -40,6 +40,14 @@ read_when: sprite's independent SwiftUI `.task(id:)` had not finished rendering. Keep transition-index diagnostics and prove a permanently stale sprite still fails after the readiness deadline. +- **Bundled CLI discovery must cover the shipped app layout.** ChatGPT moved Codex from + `Contents/Resources/codex` into `Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex`. + After an update removed the cached executable, GUI discovery returned nil and hid the limits. + Fake-response tests bypassed the production candidate list. `CodexRateLimitsProviderTests` + now checks both bundle layouts in the default candidates and preserves dedicated-install + precedence; removing the new candidate must fail the regression. The source sweep found + this provider is the only owner of ChatGPT CLI bundle paths. + - **Localized metadata names must not replace persistent API identifiers.** The dex rendered ability, move, and type slugs directly, while existing tests covered species names and profile metadata rather than these visible labels. All five detail-view name sites now use a shared